itsi-server 0.2.26-aarch64-linux → 0.2.27-aarch64-linux
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 +5 -3
- data/Rakefile +18 -5
- data/ext/itsi_acme/Cargo.toml +2 -1
- data/ext/itsi_acme/src/acceptor.rs +1 -1
- data/ext/itsi_acme/src/acme.rs +31 -3
- data/ext/itsi_acme/src/http_challenge.rs +81 -0
- data/ext/itsi_acme/src/https_helper.rs +3 -1
- data/ext/itsi_acme/src/jose.rs +6 -2
- data/ext/itsi_acme/src/lib.rs +2 -0
- data/ext/itsi_acme/src/resolver.rs +27 -4
- data/ext/itsi_acme/src/state.rs +183 -22
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +115 -64
- data/ext/itsi_scheduler/src/lib.rs +2 -1
- data/ext/itsi_server/Cargo.lock +2 -2
- data/ext/itsi_server/Cargo.toml +2 -1
- data/ext/itsi_server/src/lib.rs +15 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +0 -1
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +9 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +114 -4
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +22 -1
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +100 -0
- data/ext/itsi_server/src/server/binds/listener.rs +9 -24
- data/ext/itsi_server/src/server/binds/tls.rs +372 -67
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +3 -9
- data/ext/itsi_server/src/server/signal.rs +14 -9
- data/ext/itsi_server/src/services/itsi_http_service.rs +51 -10
- data/lib/itsi/http_request.rb +10 -0
- data/lib/itsi/server/3.1/itsi_server.so +0 -0
- data/lib/itsi/server/3.2/itsi_server.so +0 -0
- data/lib/itsi/server/3.3/itsi_server.so +0 -0
- data/lib/itsi/server/3.4/itsi_server.so +0 -0
- data/lib/itsi/server/4.0/itsi_server.so +0 -0
- data/lib/itsi/server/config/options/certificates.md +37 -9
- data/lib/itsi/server/config/options/fiber_scheduler.md +2 -0
- data/lib/itsi/server/default_config/Itsi.rb +4 -0
- data/lib/itsi/server/rack_interface.rb +47 -3
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +24 -0
- metadata +3 -2
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
use crate::default_responses::{NOT_FOUND_RESPONSE, TIMEOUT_RESPONSE};
|
|
2
2
|
use crate::ruby_types::itsi_server::itsi_server_config::ItsiServerTokenPreference;
|
|
3
3
|
use crate::server::http_message_types::{
|
|
4
|
-
ConversionExt, HttpRequest, HttpResponse, RequestExt, ResponseFormat,
|
|
4
|
+
ConversionExt, HttpBody, HttpRequest, HttpResponse, RequestExt, ResponseFormat,
|
|
5
5
|
};
|
|
6
6
|
use crate::server::lifecycle_event::LifecycleEvent;
|
|
7
7
|
use crate::server::middleware_stack::MiddlewareLayer;
|
|
8
8
|
use crate::server::serve_strategy::acceptor::AcceptorArgs;
|
|
9
9
|
use crate::server::signal::{send_lifecycle_event, SHUTDOWN_REQUESTED};
|
|
10
|
+
use bytes::Bytes;
|
|
10
11
|
use chrono::{self, DateTime, Local};
|
|
11
12
|
use either::Either;
|
|
12
13
|
use http::header::ACCEPT_ENCODING;
|
|
13
|
-
use http::{HeaderValue, Request};
|
|
14
|
+
use http::{HeaderValue, Request, StatusCode};
|
|
14
15
|
use hyper::body::Incoming;
|
|
15
16
|
use regex::Regex;
|
|
16
17
|
use smallvec::SmallVec;
|
|
@@ -174,6 +175,7 @@ impl HttpRequestContext {
|
|
|
174
175
|
const SERVER_TOKEN_VERSION: HeaderValue =
|
|
175
176
|
HeaderValue::from_static(concat!("Itsi/", env!("CARGO_PKG_VERSION")));
|
|
176
177
|
const SERVER_TOKEN_NAME: HeaderValue = HeaderValue::from_static("Itsi");
|
|
178
|
+
const TEXT_PLAIN_UTF8: HeaderValue = HeaderValue::from_static("text/plain; charset=utf-8");
|
|
177
179
|
|
|
178
180
|
impl ItsiHttpService {
|
|
179
181
|
pub async fn handle_request(&self, req: Request<Incoming>) -> itsi_error::Result<HttpResponse> {
|
|
@@ -188,14 +190,15 @@ impl ItsiHttpService {
|
|
|
188
190
|
let token_preference = self.server_params.itsi_server_token_preference;
|
|
189
191
|
|
|
190
192
|
let service_future = async move {
|
|
191
|
-
let
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
.stack_for(&req)
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
if let Some(acme_response) = self.acme_http01_response(&req) {
|
|
194
|
+
return Ok(acme_response);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let Some((stack, matching_pattern)) =
|
|
198
|
+
self.server_params.middleware.get().unwrap().stack_for(&req)
|
|
199
|
+
else {
|
|
200
|
+
return Ok(NOT_FOUND_RESPONSE.to_http_response(accept).await);
|
|
201
|
+
};
|
|
199
202
|
let mut resp: Option<HttpResponse> = None;
|
|
200
203
|
|
|
201
204
|
let mut context =
|
|
@@ -267,4 +270,42 @@ impl ItsiHttpService {
|
|
|
267
270
|
service_future.await
|
|
268
271
|
}
|
|
269
272
|
}
|
|
273
|
+
|
|
274
|
+
fn acme_http01_response(&self, req: &HttpRequest) -> Option<HttpResponse> {
|
|
275
|
+
let host = normalize_host_header(req.header("host")?)?;
|
|
276
|
+
let managers = self.server_params.acme_managers.read();
|
|
277
|
+
let key_authorization = managers
|
|
278
|
+
.iter()
|
|
279
|
+
.find_map(|(_, manager)| manager.http01_response(host, req.uri().path()))?;
|
|
280
|
+
|
|
281
|
+
let mut builder = http::Response::builder()
|
|
282
|
+
.status(StatusCode::OK)
|
|
283
|
+
.header(http::header::CONTENT_TYPE, TEXT_PLAIN_UTF8);
|
|
284
|
+
|
|
285
|
+
if req.method() == http::Method::HEAD {
|
|
286
|
+
builder = builder.header(http::header::CONTENT_LENGTH, "0");
|
|
287
|
+
return builder.body(HttpBody::empty()).ok();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
builder
|
|
291
|
+
.header(
|
|
292
|
+
http::header::CONTENT_LENGTH,
|
|
293
|
+
key_authorization.len().to_string(),
|
|
294
|
+
)
|
|
295
|
+
.body(HttpBody::full(Bytes::from(key_authorization)))
|
|
296
|
+
.ok()
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
fn normalize_host_header(host: &str) -> Option<&str> {
|
|
301
|
+
let host = host.trim();
|
|
302
|
+
if host.is_empty() {
|
|
303
|
+
return None;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if let Some(stripped) = host.strip_prefix('[') {
|
|
307
|
+
return stripped.split(']').next();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
Some(host.split(':').next().unwrap_or(host))
|
|
270
311
|
}
|
data/lib/itsi/http_request.rb
CHANGED
|
@@ -145,6 +145,16 @@ module Itsi
|
|
|
145
145
|
end
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
def partial_hijack
|
|
149
|
+
UNIXSocket.pair.yield_self do |(server_sock, app_sock)|
|
|
150
|
+
server_sock.autoclose = false
|
|
151
|
+
response.partial_hijack(server_sock.fileno)
|
|
152
|
+
server_sock.sync = true
|
|
153
|
+
app_sock.sync = true
|
|
154
|
+
app_sock
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
148
158
|
# Rack expects env["rack.hijack"] to respond to #call.
|
|
149
159
|
def call
|
|
150
160
|
hijack
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -54,12 +54,40 @@ Let's Encrypt enforces strict rate limits on production certificate generation.
|
|
|
54
54
|
{{< /callout >}}
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
Itsi supports both ACME challenge types that matter for common deployments:
|
|
58
|
+
|
|
59
|
+
* `TLS-ALPN-01` is used when the certificate authority can reach Itsi directly on the HTTPS listener.
|
|
60
|
+
* `HTTP-01` can be used when you also expose a reachable HTTP listener for the same hostname. In real Let's Encrypt deployments this typically means port `80` must reach Itsi for `/.well-known/acme-challenge/*`.
|
|
61
|
+
|
|
62
|
+
This means setups behind a CDN, WAF, or TLS-terminating proxy can still use automated certificates, provided plain HTTP validation traffic is forwarded to Itsi.
|
|
63
|
+
|
|
64
|
+
E.g. a production configuration that allows HTTP-01 fallback might look like this:
|
|
65
|
+
|
|
66
|
+
```ruby {filename=Itsi.rb}
|
|
67
|
+
bind "http://0.0.0.0:80"
|
|
68
|
+
bind "https://0.0.0.0:443?cert=acme&domains=example.com&acme_email=you@example.com"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Dynamic Domain Registration
|
|
72
|
+
You can add or remove ACME-managed domains while Itsi is already running.
|
|
73
|
+
|
|
74
|
+
This is useful when hostnames are discovered dynamically by your Ruby application, or when you want to defer certificate issuance until a tenant, customer, or site is activated.
|
|
75
|
+
|
|
76
|
+
Runtime APIs:
|
|
77
|
+
|
|
78
|
+
* `Itsi::Server.tls_bindings`
|
|
79
|
+
* `Itsi::Server.tls_domains(listener_id = nil)`
|
|
80
|
+
* `Itsi::Server.tls_domain_statuses(listener_id = nil)`
|
|
81
|
+
* `Itsi::Server.register_tls_domain(domain, listener_id = nil)`
|
|
82
|
+
* `Itsi::Server.unregister_tls_domain(domain, listener_id = nil)`
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
Itsi::Server.register_tls_domain("customer-a.example.com")
|
|
88
|
+
|
|
89
|
+
status = Itsi::Server.tls_domain_statuses.find { |entry| entry["domain"] == "customer-a.example.com" }
|
|
90
|
+
puts status
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
When using dynamic issuance with HTTP-01, the same requirement still applies: the domain being issued must be able to reach an Itsi-managed HTTP listener for the ACME challenge path.
|
|
@@ -7,6 +7,8 @@ This allows Itsi to process a very large number of IO heavy requests concurrentl
|
|
|
7
7
|
|
|
8
8
|
Enabling Fiber Scheduler mode can drastically improve application performance if you perform large amounts of blocking IO operations.
|
|
9
9
|
|
|
10
|
+
Itsi's bundled scheduler is intended to be practical for real Ruby applications, not just toy socket examples. It integrates with the scheduler hooks used by modern Rubies for socket I/O, DNS lookups, sleeps and timeouts, and process waiting.
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
## Configuration File
|
|
12
14
|
```ruby {filename="Itsi.rb"}
|
|
@@ -37,6 +37,10 @@ fiber_scheduler nil
|
|
|
37
37
|
# bind "https://itsi.fyi?cert=acme&acme_email=admin@itsi.fyi"
|
|
38
38
|
# You can generate certificates for multiple domains at once, by passing a comma-separated list of domains
|
|
39
39
|
# bind "https://0.0.0.0?domains=foo.itsi.fyi,bar.itsi.fyi&cert=acme&acme_email=admin@itsi.fyi"
|
|
40
|
+
# If HTTPS on 443 is not directly reachable, you can also expose an HTTP listener and
|
|
41
|
+
# Let's Encrypt will be able to validate using HTTP-01 instead.
|
|
42
|
+
# bind "http://0.0.0.0:80"
|
|
43
|
+
# bind "https://0.0.0.0:443?domains=foo.itsi.fyi&cert=acme&acme_email=admin@itsi.fyi"
|
|
40
44
|
#
|
|
41
45
|
# If you already have a certificate you can specify it using the cert and key parameters
|
|
42
46
|
# bind "https://itsi.fyi?cert=/path/to/cert.pem&key=/path/to/key.pem"
|
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
module Itsi
|
|
2
2
|
class Server
|
|
3
3
|
module RackInterface
|
|
4
|
+
class PartialHijackStream
|
|
5
|
+
def initialize(response)
|
|
6
|
+
@response = response
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def write(chunk)
|
|
10
|
+
@response.write(chunk.to_s)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def read(*)
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def <<(chunk)
|
|
18
|
+
write(chunk)
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def flush
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def close_write
|
|
27
|
+
@response.close_write
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def close_read
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def close
|
|
35
|
+
@response.close_write
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def closed?
|
|
39
|
+
@response.closed?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
4
43
|
# Builds a handler proc that is compatible with Rack applications.
|
|
5
44
|
def self.for(app)
|
|
6
45
|
require "rack"
|
|
@@ -38,7 +77,8 @@ module Itsi
|
|
|
38
77
|
response.status = status
|
|
39
78
|
|
|
40
79
|
# 2. Set Headers
|
|
41
|
-
|
|
80
|
+
hijack_callback = headers.delete("rack.hijack")
|
|
81
|
+
body_streamer = streaming_body?(body) ? body : hijack_callback
|
|
42
82
|
|
|
43
83
|
response.reserve_headers(headers.size)
|
|
44
84
|
|
|
@@ -57,10 +97,14 @@ module Itsi
|
|
|
57
97
|
# the server will begin to stream it to the client.
|
|
58
98
|
|
|
59
99
|
|
|
60
|
-
if
|
|
100
|
+
if hijack_callback
|
|
101
|
+
stream = status == 101 ? request.partial_hijack : PartialHijackStream.new(response)
|
|
102
|
+
body_streamer.call(stream)
|
|
103
|
+
elsif body_streamer
|
|
61
104
|
# If we're partially hijacked or returned a streaming body,
|
|
62
105
|
# stream this response.
|
|
63
|
-
|
|
106
|
+
stream = status == 101 ? request.partial_hijack : response
|
|
107
|
+
body_streamer.call(stream)
|
|
64
108
|
|
|
65
109
|
elsif body.is_a?(Array)
|
|
66
110
|
if body.length == 1
|
data/lib/itsi/server/version.rb
CHANGED
data/lib/itsi/server.rb
CHANGED
|
@@ -32,6 +32,30 @@ module Itsi
|
|
|
32
32
|
@running && !@running.empty?
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def current_server
|
|
36
|
+
@running&.last || raise("No running Itsi::Server instance")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def tls_bindings
|
|
40
|
+
current_server.tls_bindings
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def tls_domains(listener_id = nil)
|
|
44
|
+
current_server.tls_domains(listener_id)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def tls_domain_statuses(listener_id = nil)
|
|
48
|
+
current_server.tls_domain_statuses(listener_id)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def register_tls_domain(domain, listener_id = nil)
|
|
52
|
+
current_server.register_tls_domain(domain, listener_id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def unregister_tls_domain(domain, listener_id = nil)
|
|
56
|
+
current_server.unregister_tls_domain(domain, listener_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
35
59
|
def start_in_background_thread(cli_params = {}, &blk)
|
|
36
60
|
@background_threads ||= []
|
|
37
61
|
server, background_thread = start(cli_params, background: true, &blk)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: itsi-server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.27
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Wouter Coppieters
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -109,6 +109,7 @@ files:
|
|
|
109
109
|
- ext/itsi_acme/src/caches/no.rs
|
|
110
110
|
- ext/itsi_acme/src/caches/test.rs
|
|
111
111
|
- ext/itsi_acme/src/config.rs
|
|
112
|
+
- ext/itsi_acme/src/http_challenge.rs
|
|
112
113
|
- ext/itsi_acme/src/https_helper.rs
|
|
113
114
|
- ext/itsi_acme/src/incoming.rs
|
|
114
115
|
- ext/itsi_acme/src/jose.rs
|