Skip to content

Commit 1935464

Browse files
starknet_os_runner: add optional TLS termination (#12918)
1 parent 348ca25 commit 1935464

8 files changed

Lines changed: 444 additions & 39 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ time = "0.3.37"
392392
tokio = "1.47.1"
393393
tokio-metrics = "0.4.4"
394394
tokio-retry = "0.3"
395+
tokio-rustls = "0.26.4"
395396
tokio-stream = "0.1.8"
396397
tokio-test = "0.4.4"
397398
tokio-util = "0.7.13"

crates/starknet_os_runner/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ stwo_run_and_prove_lib = { workspace = true, optional = true }
4545
tempfile.workspace = true
4646
thiserror.workspace = true
4747
tokio = { workspace = true, features = ["macros", "process", "rt-multi-thread", "time"] }
48+
tokio-rustls.workspace = true
4849
tower = { workspace = true, features = ["util"] }
4950
tower-http = { workspace = true, features = ["cors"] }
5051
tracing.workspace = true

crates/starknet_os_runner/src/main.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@ fn main() {
1111
async fn main() -> anyhow::Result<()> {
1212
use std::net::SocketAddr;
1313

14-
use anyhow::Context;
1514
use clap::Parser;
16-
use jsonrpsee::server::{ServerBuilder, ServerConfig};
17-
use starknet_os_runner::server::config::{CliArgs, ServiceConfig};
15+
use starknet_os_runner::server::config::{CliArgs, ServiceConfig, TransportMode};
1816
use starknet_os_runner::server::cors::{build_cors_layer, cors_mode};
1917
use starknet_os_runner::server::rpc_impl::ProvingRpcServerImpl;
2018
use starknet_os_runner::server::rpc_trait::ProvingRpcServer;
21-
use tower::ServiceBuilder;
19+
use starknet_os_runner::server::start_server;
2220
use tracing::info;
2321
use tracing_subscriber::prelude::*;
2422
use tracing_subscriber::{fmt, EnvFilter};
@@ -36,27 +34,32 @@ async fn main() -> anyhow::Result<()> {
3634
// Build and start the JSON-RPC server.
3735
let rpc_impl = ProvingRpcServerImpl::from_config(&config);
3836
let addr = SocketAddr::new(config.ip, config.port);
39-
4037
let cors_layer = build_cors_layer(&config.cors_allow_origin)?;
4138

42-
let server_config = ServerConfig::builder().max_connections(config.max_connections).build();
43-
let server = ServerBuilder::default()
44-
.set_config(server_config)
45-
.set_http_middleware(ServiceBuilder::new().option_layer(cors_layer))
46-
.build(&addr)
47-
.await
48-
.context(format!("Failed to bind JSON-RPC server to {addr}"))?;
39+
let scheme = match &config.transport {
40+
TransportMode::Http => "http",
41+
TransportMode::Https { .. } => "https",
42+
};
43+
44+
let (local_addr, server_handle) = start_server(
45+
addr,
46+
&config.transport,
47+
rpc_impl.into_rpc().into(),
48+
config.max_connections,
49+
cors_layer,
50+
)
51+
.await?;
4952

50-
let handle = server.start(rpc_impl.into_rpc());
5153
info!(
52-
local_address = %addr,
54+
local_address = %local_addr,
55+
scheme,
5356
max_concurrent_requests = config.max_concurrent_requests,
5457
max_connections = config.max_connections,
5558
cors_mode = cors_mode(&config.cors_allow_origin),
5659
cors_allow_origin = ?config.cors_allow_origin,
5760
"JSON-RPC proving server is running."
5861
);
5962

60-
handle.stopped().await;
63+
server_handle.stopped().await;
6164
Ok(())
6265
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,51 @@
1+
use std::net::SocketAddr;
2+
3+
use anyhow::Context;
4+
use jsonrpsee::server::{Methods, ServerBuilder, ServerConfig, ServerHandle};
5+
use tower::ServiceBuilder;
6+
use tower_http::cors::CorsLayer;
7+
8+
use self::config::TransportMode;
9+
110
pub mod config;
211
pub mod cors;
312
pub mod error;
413
pub mod mock_rpc;
514
pub mod rpc_impl;
615
pub mod rpc_trait;
16+
pub mod tls;
17+
18+
/// Starts the JSON-RPC server in either HTTP or HTTPS mode depending on the transport.
19+
pub async fn start_server(
20+
addr: SocketAddr,
21+
transport: &TransportMode,
22+
methods: Methods,
23+
max_connections: u32,
24+
cors_layer: Option<CorsLayer>,
25+
) -> anyhow::Result<(SocketAddr, ServerHandle)> {
26+
match transport {
27+
TransportMode::Http => {
28+
let server_config = ServerConfig::builder().max_connections(max_connections).build();
29+
let server = ServerBuilder::default()
30+
.set_config(server_config)
31+
.set_http_middleware(ServiceBuilder::new().option_layer(cors_layer))
32+
.build(&addr)
33+
.await
34+
.context(format!("Failed to bind JSON-RPC server to {addr}"))?;
35+
let local_addr = server.local_addr()?;
36+
let server_handle = server.start(methods);
37+
Ok((local_addr, server_handle))
38+
}
39+
TransportMode::Https { tls_cert_file, tls_key_file } => {
40+
tls::start_tls_server(
41+
addr,
42+
tls_cert_file,
43+
tls_key_file,
44+
methods,
45+
max_connections,
46+
cors_layer,
47+
)
48+
.await
49+
}
50+
}
51+
}

crates/starknet_os_runner/src/server/config.rs

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Configuration for the HTTP proving service.
1+
//! Configuration for the proving service.
22
33
use std::net::{IpAddr, Ipv4Addr};
44
use std::path::PathBuf;
@@ -21,27 +21,54 @@ const DEFAULT_PORT: u16 = 3000;
2121
const DEFAULT_MAX_CONCURRENT_REQUESTS: usize = 2;
2222
const DEFAULT_MAX_CONNECTIONS: u32 = 10;
2323

24-
/// Configuration for the HTTP proving service.
24+
/// Transport mode for the JSON-RPC server.
25+
#[derive(Clone, Debug)]
26+
pub enum TransportMode {
27+
Http,
28+
Https { tls_cert_file: PathBuf, tls_key_file: PathBuf },
29+
}
30+
31+
impl TransportMode {
32+
/// Constructs a `TransportMode` from optional cert and key paths.
33+
///
34+
/// Returns `Http` when both are `None`, `Https` when both are `Some`, or an error when only
35+
/// one is provided.
36+
pub fn new(
37+
tls_cert_file: Option<PathBuf>,
38+
tls_key_file: Option<PathBuf>,
39+
) -> Result<Self, ConfigError> {
40+
match (tls_cert_file, tls_key_file) {
41+
(None, None) => Ok(Self::Http),
42+
(Some(tls_cert_file), Some(tls_key_file)) => {
43+
Ok(Self::Https { tls_cert_file, tls_key_file })
44+
}
45+
(Some(_), None) => Err(ConfigError::IncompleteTlsConfig(
46+
"tls_cert_file is set but tls_key_file is missing".to_string(),
47+
)),
48+
(None, Some(_)) => Err(ConfigError::IncompleteTlsConfig(
49+
"tls_key_file is set but tls_cert_file is missing".to_string(),
50+
)),
51+
}
52+
}
53+
}
54+
55+
/// Raw configuration as deserialized from JSON. TLS fields are optional and validated after CLI
56+
/// overrides are applied.
2557
#[derive(Clone, Debug, Deserialize, Serialize)]
2658
#[serde(default)]
27-
pub struct ServiceConfig {
28-
/// Configuration for the prover.
59+
struct RawServiceConfig {
2960
#[serde(flatten)]
30-
pub prover_config: ProverConfig,
31-
/// IP address to bind the server to.
32-
pub ip: IpAddr,
33-
/// Port to bind the server to.
34-
pub port: u16,
35-
/// Maximum number of concurrent proving requests.
36-
pub max_concurrent_requests: usize,
37-
/// Maximum number of simultaneous JSON-RPC connections (safety net).
38-
pub max_connections: u32,
39-
/// List of allowed web origins (domains) that may call this HTTP service from a browser
40-
/// (CORS). Examples: `http://localhost:5173`, `https://app.example.com`, or `*` to allow any origin.
41-
pub cors_allow_origin: Vec<String>,
61+
prover_config: ProverConfig,
62+
ip: IpAddr,
63+
port: u16,
64+
max_concurrent_requests: usize,
65+
max_connections: u32,
66+
cors_allow_origin: Vec<String>,
67+
tls_cert_file: Option<PathBuf>,
68+
tls_key_file: Option<PathBuf>,
4269
}
4370

44-
impl Default for ServiceConfig {
71+
impl Default for RawServiceConfig {
4572
fn default() -> Self {
4673
Self {
4774
prover_config: ProverConfig::default(),
@@ -50,10 +77,33 @@ impl Default for ServiceConfig {
5077
max_concurrent_requests: DEFAULT_MAX_CONCURRENT_REQUESTS,
5178
max_connections: DEFAULT_MAX_CONNECTIONS,
5279
cors_allow_origin: Vec::new(),
80+
tls_cert_file: None,
81+
tls_key_file: None,
5382
}
5483
}
5584
}
5685

86+
/// Configuration for the proving service.
87+
#[derive(Clone, Debug)]
88+
pub struct ServiceConfig {
89+
/// Configuration for the prover.
90+
pub prover_config: ProverConfig,
91+
/// IP address to bind the server to.
92+
pub ip: IpAddr,
93+
/// Port to bind the server to.
94+
pub port: u16,
95+
/// Maximum number of concurrent proving requests.
96+
pub max_concurrent_requests: usize,
97+
/// Maximum number of simultaneous JSON-RPC connections (safety net).
98+
pub max_connections: u32,
99+
/// List of allowed web origins (domains) that may call this HTTP service from a browser
100+
/// (CORS). Examples: `http://localhost:5173`, `https://app.example.com`, or `*` to allow any
101+
/// origin.
102+
pub cors_allow_origin: Vec<String>,
103+
/// Transport mode (HTTP or HTTPS with TLS).
104+
pub transport: TransportMode,
105+
}
106+
57107
impl ServiceConfig {
58108
/// Creates a ServiceConfig from CLI arguments.
59109
pub fn from_args(args: CliArgs) -> Result<Self, ConfigError> {
@@ -65,15 +115,15 @@ impl ServiceConfig {
65115
e
66116
))
67117
})?;
68-
serde_json::from_str(&contents).map_err(|e| {
118+
serde_json::from_str::<RawServiceConfig>(&contents).map_err(|e| {
69119
ConfigError::ConfigFileError(format!(
70120
"Failed to parse config file {}: {}",
71121
config_file.display(),
72122
e
73123
))
74124
})?
75125
} else {
76-
ServiceConfig::default()
126+
RawServiceConfig::default()
77127
};
78128

79129
// Override with CLI arguments if provided.
@@ -126,6 +176,24 @@ impl ServiceConfig {
126176
config.max_connections = max;
127177
}
128178
}
179+
if let Some(tls_cert_file) = args.tls_cert_file {
180+
if Some(&tls_cert_file) != config.tls_cert_file.as_ref() {
181+
info!(
182+
"CLI override: tls_cert_file: {:?} -> {:?}",
183+
config.tls_cert_file, tls_cert_file
184+
);
185+
config.tls_cert_file = Some(tls_cert_file);
186+
}
187+
}
188+
if let Some(tls_key_file) = args.tls_key_file {
189+
if Some(&tls_key_file) != config.tls_key_file.as_ref() {
190+
info!(
191+
"CLI override: tls_key_file: {:?} -> {:?}",
192+
config.tls_key_file, tls_key_file
193+
);
194+
config.tls_key_file = Some(tls_key_file);
195+
}
196+
}
129197

130198
if args.no_cors && !args.cors_allow_origin.is_empty() {
131199
return Err(ConfigError::InvalidArgument(
@@ -194,19 +262,28 @@ impl ServiceConfig {
194262
"max_connections must be at least 1".to_string(),
195263
));
196264
}
197-
config.cors_allow_origin = normalize_cors_allow_origins(config.cors_allow_origin)?;
198-
if config.cors_allow_origin == ["*"] {
265+
let transport = TransportMode::new(config.tls_cert_file, config.tls_key_file)?;
266+
let cors_allow_origin = normalize_cors_allow_origins(config.cors_allow_origin)?;
267+
if cors_allow_origin == ["*"] {
199268
info!("CORS allow-origin configured as wildcard '*'.");
200269
}
201270

202-
Ok(config)
271+
Ok(ServiceConfig {
272+
prover_config: config.prover_config,
273+
ip: config.ip,
274+
port: config.port,
275+
max_concurrent_requests: config.max_concurrent_requests,
276+
max_connections: config.max_connections,
277+
cors_allow_origin,
278+
transport,
279+
})
203280
}
204281
}
205282

206283
/// CLI arguments for the proving service.
207284
#[derive(Parser, Debug)]
208285
#[command(name = "starknet-os-runner")]
209-
#[command(about = "HTTP service for generating Starknet OS proofs", long_about = None)]
286+
#[command(about = "HTTP/HTTPS service for generating Starknet OS proofs", long_about = None)]
210287
pub struct CliArgs {
211288
/// Path to JSON configuration file.
212289
#[arg(long, value_name = "FILE")]
@@ -236,6 +313,14 @@ pub struct CliArgs {
236313
#[arg(long, value_name = "N")]
237314
pub max_connections: Option<u32>,
238315

316+
/// Path to TLS certificate chain PEM file. Requires --tls-key-file.
317+
#[arg(long, value_name = "FILE")]
318+
pub tls_cert_file: Option<PathBuf>,
319+
320+
/// Path to TLS private key PEM file. Requires --tls-cert-file.
321+
#[arg(long, value_name = "FILE")]
322+
pub tls_key_file: Option<PathBuf>,
323+
239324
/// Override STRK fee token address (hex, e.g. for custom environments that share a chain ID).
240325
#[arg(long, value_name = "ADDRESS")]
241326
pub strk_fee_token_address: Option<String>,
@@ -279,4 +364,6 @@ pub enum ConfigError {
279364
InvalidArgument(String),
280365
#[error("Missing required field: {0}")]
281366
MissingRequiredField(String),
367+
#[error("Incomplete TLS configuration: {0}")]
368+
IncompleteTlsConfig(String),
282369
}

0 commit comments

Comments
 (0)