1use futures_util::FutureExt;
6mod ops;
7
8use axum::http::{HeaderValue, Uri};
9use axum::http::{Request, header};
10use axum::response::Redirect;
11use axum::{Extension, extract::ConnectInfo};
12use hashbrown::HashMap;
13use hyper::HeaderMap;
14use hyper::body::Incoming;
15use hyper_util::rt::{TokioExecutor, TokioIo};
16use ordinary_app::server::OrdinaryAppServer;
17#[cfg(feature = "server")]
18use ordinary_config::OrdinaryApiLimits;
19use ordinary_monitor::Manager;
20use ordinary_storage::Storage;
21use ordinary_storage::saferlmdb::{self, EnvBuilder, Environment};
22use parking_lot::RwLock;
23use rand_chacha::rand_core::Rng;
24use rand_chacha::rand_core::SeedableRng;
25use sha2::{Digest, Sha256};
26use std::fs::File;
27use std::io::Write;
28use std::process;
29use tokio::net::TcpListener;
30use tokio::net::TcpStream;
31use tokio_rustls::LazyConfigAcceptor;
32use tokio_rustls::StartHandshake;
33use tower::Layer;
34
35use axum::Router;
36use axum::http::HeaderName;
37use axum::response::Response;
38use axum::routing::{get, patch, post, put};
39
40use axum::http::StatusCode;
41use tower_http::classify::ServerErrorsFailureClass;
42use tower_http::request_id::{MakeRequestUuid, PropagateRequestIdLayer, SetRequestIdLayer};
43use uuid::Uuid;
44
45use std::net::SocketAddr;
46use std::path::Path;
47use std::path::PathBuf;
48use std::sync::Arc;
49use std::time::Duration;
50
51use parking_lot::Mutex;
52
53use rustls_acme::is_tls_alpn_challenge;
54use rustls_acme::{AcmeConfig, caches::DirCache};
55use tokio::io::AsyncWriteExt;
56use tokio_rustls::{TlsAcceptor, rustls::ServerConfig};
57use tower::{Service, ServiceBuilder};
58use tower_http::compression::CompressionLayer;
59use tower_http::decompression::RequestDecompressionLayer;
60use tower_http::trace::TraceLayer;
61use tracing::Span;
62
63use ordinary_auth::{Auth, AuthClient};
64
65use std::fs;
66
67use crate::{api_account_claims, api_invite_claims};
68use axum::middleware::Next;
69use base64::{Engine as B64Engine, engine::general_purpose::URL_SAFE_NO_PAD as b64};
70use getrandom::SysRng;
71use hickory_client::client::Client;
72use hickory_client::proto::runtime::TokioRuntimeProvider;
73use hickory_client::proto::udp::UdpClientStream;
74use ordinary_config::{
75 AccessTokenConfig, AuthConfig, HmacTokenAlgorithm, InviteConfig, InviteMode, MfaConfig,
76 OrdinaryApiConfig, OrdinaryConfig, PasswordConfig, RedactedHashAlg, RefreshTokenConfig,
77 SignedTokenAlgorithm, TotpConfig,
78};
79use ordinary_utils::middleware::modify_etag_for_encoding;
80use ordinary_utils::{
81 GMT_FORMAT, HeadersDebug, LatencyDisplay, REQUEST_ID_HEADER, WrappedRedactedHashingAlg,
82 get_host, redirect_service, response_for_panic, rustls_server_config, shutdown_signal,
83};
84use std::error::Error;
85use std::pin::pin;
86use time::UtcDateTime;
87use tokio::sync::watch;
88use tokio::sync::watch::{Receiver, Sender};
89use tokio::time::timeout;
90use tokio_rustls::rustls::crypto::ring;
91use tokio_rustls::rustls::server::Acceptor;
92use tokio_rustls::server::TlsStream;
93use tower_http::catch_panic::CatchPanicLayer;
94use tower_http::set_header::SetResponseHeaderLayer;
95use tower_http::timeout::TimeoutLayer;
96use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
97use utoipa::{Modify, OpenApi};
98use utoipa_swagger_ui::{Config, SwaggerUi};
99
100pub const ROOT: &str = "root";
101pub const ADMIN: &str = "admin";
102pub const APPLICATION: &str = "application";
103pub const AUTHENTICATION: &str = "authentication";
104
105#[derive(OpenApi)]
106#[openapi(
107 info(
108 title = "Ordinary API Server",
109 description = "
110## ordinary-api
111
112[](https://docs.rs/ordinary-api/0.6.0-pre.3)
113[](https://deps.rs/crate/ordinary-api/0.6.0-pre.3)
114
115### Access Token
116
117Use the [Ordinary CLI](https://crates.io/crates/ordinary) to get an access token.
118
119```sh
120ordinary accounts access get --min 15
121```
122",
123 contact(),
124 ),
125 modifiers(&Security),
126 nest(
127 (path = "/v1", api = OrdinaryApiOpenApi),
128 ),
129 tags(
130 (name = APPLICATION, description = "Application API"),
131 (name = ADMIN, description = "Admin API"),
132 (name = ROOT, description = "Root API"),
133 (name = AUTHENTICATION, description = "Authentication API"),
134 )
135)]
136struct ApiDoc;
137
138struct Security;
139
140impl Modify for Security {
141 fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
142 if let Some(components) = openapi.components.as_mut() {
143 components.add_security_scheme(
144 "access",
145 SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
146 );
147 }
148 }
149}
150
151#[derive(OpenApi)]
152#[openapi(paths(
153 ops::accounts::invite,
154 ops::accounts::list,
155 ops::accounts::delete,
156 ops::auth::registration_start,
157 ops::auth::registration_finish,
158 ops::auth::login_start,
159 ops::auth::login_finish,
160 ops::logs::root,
161 ops::logs::app,
162 ops::app::deploy,
163 ops::app::patch,
164 ops::app::kill,
165 ops::templates::upload,
166 ops::content::update,
167 ops::assets::write,
168 ops::models::items_list,
169 ops::actions::install,
170))]
171pub struct OrdinaryApiOpenApi;
172
173pub(crate) fn check_ordinary_auth(
175 state: &OrdinaryApiServerState,
176 headers: &HeaderMap,
177 permission: u8,
178 domain: &str,
179) -> Result<String, StatusCode> {
180 if let Some(auth_header) = headers.get("authorization")
181 && let Ok(str_val) = auth_header.to_str()
182 && let Some(b64_token) = str_val.strip_prefix("Bearer ")
183 && let Ok(token) = b64.decode(b64_token)
184 {
185 match state.auth.verify_access_token(&token) {
186 Ok((account, claims)) => {
187 let span = tracing::info_span!("permission", account);
188
189 span.in_scope(|| {
190 if account == "root" {
191 return Ok(account.to_string());
192 }
193
194 if domain == "root" {
195 tracing::error!("root-only operation disallowed");
196 return Err(StatusCode::UNAUTHORIZED);
197 }
198
199 let check_domain = claims.idx(1).as_str();
200
201 if check_domain != domain {
202 tracing::error!(domain, "token domain '{check_domain}' does not match");
203 return Err(StatusCode::UNAUTHORIZED);
204 }
205
206 let permissions = claims.idx(2).as_vector();
207
208 for claim_permission in &permissions {
209 if claim_permission.as_u8() == 0 || claim_permission.as_u8() == permission {
210 tracing::info!(
211 role = match claim_permission.as_u8() {
212 0 => "admin",
213 1 => "read",
214 2 => "write",
215 3 => "update",
216 4 => "upload",
217 5 => "install",
218 6 => "deploy",
219 7 => "bridge",
220 8 => "kill",
221 9 => "erase",
222 _ => "invalid",
223 },
224 "granted"
225 );
226
227 return Ok(account.to_string());
228 }
229 }
230
231 tracing::error!(
232 permission = match permission {
233 0 => "admin",
234 1 => "read",
235 2 => "write",
236 3 => "update",
237 4 => "upload",
238 5 => "install",
239 6 => "deploy",
240 7 => "bridge",
241 8 => "kill",
242 9 => "erase",
243 _ => "invalid",
244 },
245 "not in token permissions"
246 );
247 Err(StatusCode::UNAUTHORIZED)
248 })
249 }
250 Err(err) => {
251 tracing::error!(%err, "access denied");
252 Err(StatusCode::UNAUTHORIZED)
253 }
254 }
255 } else {
256 tracing::error!("invalid token");
257 Err(StatusCode::UNAUTHORIZED)
258 }
259}
260
261pub struct WrappedOrdinaryAppServer {
262 port: u16,
263 app: Arc<OrdinaryAppServer>,
264 kill_tx: tokio::sync::broadcast::Sender<()>,
265 stream_tx: tokio::sync::mpsc::UnboundedSender<(StartHandshake<TcpStream>, SocketAddr, Span)>,
266}
267
268type Dbs = HashMap<String, (Arc<Environment>, Arc<Auth>, Arc<Storage>)>;
269
270#[allow(clippy::struct_excessive_bools)]
271pub struct OrdinaryApiServerState {
272 pub domain: String,
273 pub secure: bool,
274 pub secure_cookies: bool,
275 pub log_headers: bool,
276 pub log_ips: bool,
277 pub log_size: bool,
278 pub auth: Arc<Auth>,
279 pub apps: Arc<RwLock<HashMap<String, Arc<WrappedOrdinaryAppServer>>>>,
280 pub apps_dir: PathBuf,
281 pub dbs: Arc<Mutex<Dbs>>,
282 pub env_name: String,
283 pub provision_mode: Option<ProvisionMode>,
284 pub dedicated_ports: bool,
285 pub server_span: Span,
286 pub log_manager: Arc<Option<Manager>>,
287 pub log_index: bool,
288 pub limits: String,
289 pub config: OrdinaryApiConfig,
290
291 pub signal_tx: Arc<RwLock<Option<Sender<()>>>>,
292 pub close_rx: Arc<RwLock<Option<Receiver<()>>>>,
293
294 pub dns_client: Arc<tokio::sync::Mutex<Client>>,
295}
296
297#[derive(PartialEq, Clone)]
298pub enum ProvisionMode {
299 Localhost,
300 Staging,
301 Production,
302}
303
304pub enum SecurityMode<T: AsRef<Path>> {
305 Insecure,
306 Secure(T, ProvisionMode),
307}
308
309pub struct OrdinaryApiServer {
310 auth: Arc<Auth>,
311 _env: Arc<Environment>,
312 config: OrdinaryApiConfig,
313 apps: Arc<RwLock<HashMap<String, Arc<WrappedOrdinaryAppServer>>>>,
314 apps_dir: PathBuf,
315 log_manager: Arc<Option<Manager>>,
316}
317
318#[cfg(feature = "server")]
319impl OrdinaryApiServer {
320 #[allow(clippy::similar_names)]
321 pub fn init(
322 env_name: &str,
323 domain: &str,
324 password: &str,
325 base_dir_path: impl AsRef<Path>,
326 storage_size: usize,
327 log_manager: Arc<Option<Manager>>,
328 ) -> Result<(String, Vec<u8>), Box<dyn Error>> {
329 let span = tracing::info_span!("init", env = %env_name, pid = process::id());
330
331 span.in_scope(|| {
332 let config = OrdinaryApiConfig {
333 public_dns_ip: [8, 8, 8, 8],
335 env_name: env_name.to_string(),
336 limits: OrdinaryApiLimits::default(),
337 };
338 let config_file = serde_json::to_string_pretty(&config)?;
339
340 let environment_dir_path = base_dir_path.as_ref().join("environments").join(env_name);
341 let data_path = environment_dir_path.join("data");
342
343 if data_path.exists() {
344 return Err("environment already initialized".into());
345 }
346
347 fs::write(environment_dir_path.join("ordinaryd.json"), config_file)?;
348
349 let api_server =
350 OrdinaryApiServer::new(env_name, domain, base_dir_path, storage_size, log_manager)?;
351
352 let mut input = domain.as_bytes().to_vec();
353 input.extend_from_slice(b"root");
354
355 let mut password_input = input.clone();
356 password_input.extend_from_slice(password.as_bytes());
357
358 let mut hasher = Sha256::new();
359 hasher.update(&password_input);
360 let password = hasher.finalize().to_vec();
361
362 let invite_token = api_server.auth.api_invite_get(domain, "root", None)?;
363
364 let (state, reg_start_req) =
365 AuthClient::registration_start_req(b"root", &password, Some(invite_token))?;
366 let reg_start_res = api_server
367 .auth
368 .registration_start(reg_start_req, None, None)?;
369
370 let (private_key, reg_finish_req) =
371 AuthClient::registration_finish_req(b"root", &password, &state, ®_start_res)?;
372 let (reg_finish_res, ..) = api_server.auth.registration_finish(reg_finish_req, None)?;
373
374 let (totp, _recovery_codes) = AuthClient::decrypt_totp_mfa(
375 ®_finish_res,
376 private_key,
377 env_name.to_string(),
378 "root".into(),
379 )?;
380
381 Ok((totp.get_url(), totp.secret))
382 })
383 }
384
385 pub fn new(
386 env_name: &str,
387 domain: &str,
388 base_dir_path: impl AsRef<Path>,
389 storage_size: usize,
390 log_manager: Arc<Option<Manager>>,
391 ) -> Result<OrdinaryApiServer, Box<dyn Error>> {
392 use ordinary_config::{HmacTokenConfig, SignedTokenConfig};
393
394 let span = tracing::info_span!("setup", env = %env_name, pid = process::id());
395
396 span.in_scope(|| {
397 let environment_dir_path = base_dir_path.as_ref().join("environments").join(env_name);
398
399 let config: OrdinaryApiConfig = serde_json::from_str(&fs::read_to_string(
400 environment_dir_path.join("ordinaryd.json"),
401 )?)?;
402
403 let data_path = environment_dir_path.join("data");
404
405 fs::create_dir_all(&data_path)?;
406
407 let ps = page_size::get();
408
409 let remainder = storage_size % ps;
411 let mapsize = (storage_size - remainder) + ps;
412
413 tracing::info!(mapsize = %bytesize::ByteSize(mapsize as u64).display().si_short());
414
415 let env = Arc::new(unsafe {
416 let mut env_builder = EnvBuilder::new()?;
417 env_builder.set_maxreaders(126)?;
418 env_builder.set_mapsize(mapsize)?;
419 env_builder.set_maxdbs(13)?;
420 env_builder.open(
421 match data_path.to_str() {
422 Some(v) => v,
423 None => return Err("data_path not a str".into()),
424 },
425 &saferlmdb::open::Flags::empty(),
426 0o600,
427 )?
428 });
429
430 let keys_dir = environment_dir_path.join("keys");
431 fs::create_dir_all(&keys_dir)?;
432
433 let auth_key_path = keys_dir.join("auth");
434
435 let auth_key: [u8; 32] = if auth_key_path.exists() && auth_key_path.is_file() {
436 let auth_key = fs::read(&auth_key_path)?;
437 let auth_key: [u8; 32] = auth_key[..].try_into()?;
438 auth_key
439 } else {
440 let mut auth_key = [0u8; 32];
441 let mut rng = rand_chacha::ChaCha20Rng::try_from_rng(&mut SysRng)?;
442
443 rng.fill_bytes(&mut auth_key[..]);
444
445 let mut auth_key_file = File::create(auth_key_path)?;
446 auth_key_file.write_all(&auth_key)?;
447
448 auth_key
449 };
450
451 let auth = Arc::new(Auth::new(
452 domain.into(),
453 Some(AuthConfig {
454 password: PasswordConfig {
455 protocol: ordinary_config::PasswordProtocol::Opaque,
456 },
457 mfa: MfaConfig {
458 totp: TotpConfig {
459 template: None,
460 algorithm: ordinary_config::TotpAlgorithm::Sha1,
461 },
462 },
463 hmac_token: HmacTokenConfig {
464 algorithm: HmacTokenAlgorithm::HmacBlake2b256,
465 rotation: 60 * 60 * 24 * 7,
466 },
467 signed_token: SignedTokenConfig {
468 algorithm: SignedTokenAlgorithm::DsaEd25519,
469 rotation: 60 * 60 * 24 * 30,
470 },
471 refresh_token: RefreshTokenConfig {
472 lifetime: 60 * 60 * 24 * 7,
473 },
474 access_token: AccessTokenConfig {
475 lifetime: 60 * 60 * 24,
476 claims: api_account_claims(),
477 },
478 client_hash: ordinary_config::ClientPasswordHash::Sha256,
479 cookies_enabled: true,
480 invite: Some(InviteConfig {
481 mode: InviteMode::Viral,
482 lifetime: 60 * 60 * 24,
483 clean_interval: (30, 90),
484 claims: Some(api_invite_claims()),
485 }),
486 }),
487 auth_key,
488 env.clone(),
489 )?);
490
491 let apps_dir = environment_dir_path.join("apps");
492 fs::create_dir_all(&apps_dir)?;
493
494 Ok(OrdinaryApiServer {
495 config,
496 auth,
497 _env: env,
498 apps: Arc::new(RwLock::new(HashMap::new())),
499 apps_dir,
500 log_manager,
501 })
502 })
503 }
504
505 #[allow(
506 clippy::too_many_arguments,
507 clippy::fn_params_excessive_bools,
508 clippy::too_many_lines
509 )]
510 pub async fn start<P, F>(
511 &self,
512 server_span: Span,
513 mode: SecurityMode<P>,
514 listener: TcpListener,
515 secure_cookies: bool,
516 log_headers: bool,
517 log_ips: bool,
518 log_size: bool,
519 api_domain: String,
520 api_contacts: &[String],
521 redirect_listener: Option<TcpListener>,
522 dedicated_ports: bool,
523 log_index: bool,
524 redacted_hash: Option<RedactedHashAlg>,
525 swagger: bool,
526 signal: fn() -> F,
527 ) -> Result<(), Box<dyn Error>>
528 where
529 P: AsRef<Path>,
530 F: Future<Output = ()> + Send + 'static,
531 {
532 use axum::extract::State;
533
534 let start_span = tracing::info_span!(
535 "start",
536 env = &self.config.env_name,
537 pid = process::id(),
538 domain = api_domain
539 );
540
541 let address = SocketAddr::from((self.config.public_dns_ip, 53));
542 let conn = UdpClientStream::builder(address, TokioRuntimeProvider::default()).build();
543
544 let (client, bg) = Client::connect(conn).await?;
545 tokio::spawn(bg);
546
547 let server_span_clone = server_span.clone();
548 let server_span_clone2 = server_span.clone();
549 let server_span_clone3 = server_span.clone();
550
551 let api_domain_clone = api_domain.clone();
552
553 let limits = serde_json::to_string(&self.config.limits)?;
554
555 let state = start_span.in_scope(|| {
556 Arc::new(OrdinaryApiServerState {
557 domain: api_domain.clone(),
558 secure: !matches!(mode, SecurityMode::Insecure),
559 secure_cookies,
560 log_headers,
561 log_ips,
562 log_size,
563 auth: self.auth.clone(),
564 apps: self.apps.clone(),
565 apps_dir: self.apps_dir.clone(),
566 dbs: Arc::new(Mutex::new(HashMap::new())),
567 env_name: self.config.env_name.clone(),
568 provision_mode: match &mode {
569 SecurityMode::Insecure => None,
570 SecurityMode::Secure(_, provision) => Some(provision.clone()),
571 },
572 dedicated_ports,
573 server_span: server_span.clone(),
574 log_manager: self.log_manager.clone(),
575 log_index,
576 limits,
577 config: self.config.clone(),
578
579 signal_tx: Arc::new(RwLock::new(None)),
580 close_rx: Arc::new(RwLock::new(None)),
581
582 dns_client: Arc::new(tokio::sync::Mutex::new(client)),
583 })
584 });
585
586 let request_id = HeaderName::from_static(REQUEST_ID_HEADER);
587
588 let redacted_hash = if let Some(redacted_hash) = redacted_hash {
589 Arc::new(Some(WrappedRedactedHashingAlg(redacted_hash)))
590 } else {
591 Arc::new(None)
592 };
593
594 let redacted_hash_clone = redacted_hash.clone();
595 let redacted_hash_clone1 = redacted_hash.clone();
596
597 let last_modified = UtcDateTime::now();
598
599 let last_modified_string = last_modified.format(&GMT_FORMAT)?;
600 let last_modified_header = HeaderValue::from_str(last_modified_string.as_str())?;
601
602 let ordinaryd_resource_layers = ServiceBuilder::new()
603 .layer(axum::middleware::from_fn(
604 move |req_headers: HeaderMap, request: axum::extract::Request, next: Next| {
605 ordinary_utils::middleware::http_cache_middleware(
606 last_modified,
607 req_headers,
608 request,
609 next,
610 )
611 },
612 ))
613 .layer(SetResponseHeaderLayer::if_not_present(
614 header::LAST_MODIFIED,
615 last_modified_header.clone(),
616 ))
617 .layer(SetResponseHeaderLayer::if_not_present(
618 header::EXPIRES,
619 |_: &Response<_>| {
620 let future = UtcDateTime::now() + time::Duration::minutes(10);
621
622 if let Ok(formatted) = future.format(&GMT_FORMAT)
623 && let Ok(expires) = HeaderValue::from_str(formatted.as_str())
624 {
625 return Some(expires);
626 }
627
628 None
629 },
630 ))
631 .layer(SetResponseHeaderLayer::if_not_present(
632 header::VARY,
633 HeaderValue::from_static(header::ACCEPT_ENCODING.as_str()),
634 ));
635
636 let mut service = Router::new();
637
638 if swagger {
639 service = service
644 .merge(
645 SwaggerUi::new("/.ordinary/swagger").config(Config::from("/.ordinary/openapi")),
646 )
647 .route_layer(ordinaryd_resource_layers.clone().layer(
648 SetResponseHeaderLayer::if_not_present(
649 header::CONTENT_SECURITY_POLICY,
650 HeaderValue::from_static(
651 "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src https://img.shields.io",
652 ),
653 ),
654 ));
655 }
656
657 if swagger {
659 let api = ApiDoc::openapi().to_json()?;
660
661 service = service.route(
662 "/.ordinary/openapi",
663 get(|| async move {
664 (
665 StatusCode::OK,
666 [(header::CONTENT_TYPE, "application/json")],
667 api,
668 )
669 })
670 .route_layer(ordinaryd_resource_layers.clone()),
671 );
672 }
673
674 service = service.route(
675 "/.ordinary/limits",
676 get(
677 |State(state): State<Arc<OrdinaryApiServerState>>| async move {
678 (
679 StatusCode::OK,
680 [(header::CONTENT_TYPE, "application/json")],
681 state.limits.clone(),
682 )
683 },
684 )
685 .route_layer(ordinaryd_resource_layers.clone()),
686 );
687
688 let mut service = service
689 .route("/healthz", get(|| async { StatusCode::OK }))
690 .route(
692 "/v1/accounts/registration/start",
693 put(ops::auth::registration_start),
694 )
695 .route(
696 "/v1/accounts/registration/finish",
697 put(ops::auth::registration_finish),
698 )
699 .route("/v1/accounts/login/start", put(ops::auth::login_start))
700 .route("/v1/accounts/login/finish", put(ops::auth::login_finish))
701 .route("/v1/accounts/access/get", put(ops::auth::access_get))
702 .route("/v1/accounts/logout", put(ops::auth::logout))
703 .route(
705 "/v1/accounts/password/reset/login/start",
706 put(ops::auth::password_reset_login_start),
707 )
708 .route(
709 "/v1/accounts/password/reset/login/finish",
710 put(ops::auth::password_reset_login_finish),
711 )
712 .route(
713 "/v1/accounts/password/reset/registration/start",
714 put(ops::auth::password_reset_registration_start),
715 )
716 .route(
717 "/v1/accounts/password/reset/registration/finish",
718 put(ops::auth::password_reset_registration_finish),
719 )
720 .route(
722 "/v1/accounts/password/forgot/start",
723 put(ops::auth::password_forgot_start),
724 )
725 .route(
726 "/v1/accounts/password/forgot/finish",
727 put(ops::auth::password_forgot_finish),
728 )
729 .route(
731 "/v1/accounts/mfa/totp/reset/start",
732 put(ops::auth::mfa_reset_totp_start),
733 )
734 .route(
735 "/v1/accounts/mfa/totp/reset/finish",
736 put(ops::auth::mfa_reset_totp_finish),
737 )
738 .route(
740 "/v1/accounts/mfa/totp/lost/start",
741 put(ops::auth::mfa_lost_totp_start),
742 )
743 .route(
744 "/v1/accounts/mfa/totp/lost/finish",
745 put(ops::auth::mfa_lost_totp_finish),
746 )
747 .route(
749 "/v1/accounts/delete/start",
750 put(ops::auth::account_delete_start),
751 )
752 .route(
753 "/v1/accounts/delete/finish",
754 put(ops::auth::account_delete_finish),
755 )
756 .route(
758 "/v1/accounts/recovery-codes/reset/start",
759 put(ops::auth::recovery_reset_codes_start),
760 )
761 .route(
762 "/v1/accounts/recovery-codes/reset/finish",
763 put(ops::auth::recovery_reset_codes_finish),
764 )
765 .route("/v1/apps", get(ops::app::list))
767 .route("/v1/logs", get(ops::logs::root))
768 .route("/v1/accounts/invite", put(ops::accounts::invite))
770 .route("/v1/accounts", get(ops::accounts::list))
771 .route("/v1/accounts/delete", put(ops::accounts::delete))
772 .route("/v1/app/accounts/invite", put(ops::app::invite))
774 .route("/v1/app/accounts", get(ops::app::accounts_list))
775 .route("/v1/app/deploy", post(ops::app::deploy))
777 .route("/v1/app/patch", patch(ops::app::patch))
778 .route("/v1/app/migrate", put(ops::app::migrate))
779 .route("/v1/app/bridge", put(ops::app::bridge))
780 .route("/v1/app/kill", put(ops::app::kill))
781 .route("/v1/app/erase", put(ops::app::erase))
782 .route("/v1/app/logs", get(ops::logs::app))
783 .route("/v1/templates", put(ops::templates::upload))
785 .route("/v1/content", put(ops::content::update))
787 .route("/v1/assets", put(ops::assets::write))
789 .route("/v1/actions", put(ops::actions::install))
791 .route("/v1/models/items", get(ops::models::items_list))
793 .fallback(|| async { StatusCode::NOT_FOUND })
794 .with_state(state.clone())
795 .layer(
796 ServiceBuilder::new()
797 .layer(CatchPanicLayer::custom(response_for_panic))
798 .layer(RequestDecompressionLayer::new())
799 .layer(CompressionLayer::new()),
800 )
801 .layer(
802 ServiceBuilder::new()
803 .layer(SetRequestIdLayer::new(request_id.clone(), MakeRequestUuid))
804 .layer(
805 TraceLayer::new_for_http()
806 .make_span_with(move |req: &Request<_>| {
807 let request_id = req.headers().get(REQUEST_ID_HEADER);
808
809 let ip = log_ips.then(|| {
810 req.extensions()
811 .get::<ConnectInfo<SocketAddr>>()
812 .map(|addr| tracing::field::display(addr.ip()))
813 });
814
815 let query = req.uri().query().map(tracing::field::display);
816
817 server_span_clone.in_scope(|| match request_id {
818 Some(rid) => {
819 tracing::info_span!(
820 "admin",
821 domain = %api_domain_clone,
822 id = %rid
823 .to_str()
824 .unwrap_or(Uuid::new_v4().to_string().as_str()),
825 ip,
826 path = %req.uri().path(),
827 query,
828 )
829 }
830 None => {
831 tracing::info_span!(
832 "admin",
833 domain = %api_domain_clone,
834 id = %Uuid::new_v4(),
835 ip,
836 path = %req.uri().path(),
837 query,
838 )
839 }
840 })
841 })
842 .on_request(move |req: &Request<_>, _: &Span| {
843 let hd = log_headers
844 .then_some(HeadersDebug(req.headers(), redacted_hash.clone()));
845
846 #[cfg(tracing_unstable)]
847 let headers = log_headers.then_some(tracing::field::valuable(&hd));
848
849 #[cfg(not(tracing_unstable))]
850 let headers = log_headers.then_some(tracing::field::debug(&hd));
851
852 tracing::info!(
853 version = ?req.version(),
854 method = %req.method(),
855 headers,
856 "req"
857 );
858 })
859 .on_response(move |res: &Response<_>, latency: Duration, _: &Span| {
860 let hd = log_headers.then_some(HeadersDebug(
861 res.headers(),
862 redacted_hash_clone.clone(),
863 ));
864
865 #[cfg(tracing_unstable)]
866 let headers = log_headers.then_some(tracing::field::valuable(&hd));
867
868 #[cfg(not(tracing_unstable))]
869 let headers = log_headers.then_some(tracing::field::debug(&hd));
870
871 let status = res.status().as_u16();
872 let latency = LatencyDisplay(latency.as_nanos() as f64);
873
874 if status >= 500 {
875 tracing::error!(status, headers, %latency, "res");
876 } else if status >= 400 {
877 tracing::warn!(status, headers, %latency, "res");
878 } else {
879 tracing::info!(status, headers, %latency, "res");
880 }
881 })
882 .on_failure(
883 |error: ServerErrorsFailureClass, _: Duration, _: &Span| {
884 tracing::error!(
885 err = %error,
886 "fail"
887 );
888 },
889 ),
890 )
891 .layer(TimeoutLayer::with_status_code(
892 StatusCode::REQUEST_TIMEOUT,
893 Duration::from_secs(10),
894 ))
895 .layer(PropagateRequestIdLayer::new(request_id))
896 .layer(SetResponseHeaderLayer::if_not_present(
897 header::SERVER,
898 HeaderValue::from_static("Ordinary"),
899 ))
900 .layer(SetResponseHeaderLayer::overriding(
901 header::ETAG,
902 modify_etag_for_encoding,
903 )),
904 );
905
906 if state.secure {
907 service = service.layer(SetResponseHeaderLayer::if_not_present(
908 header::STRICT_TRANSPORT_SECURITY,
909 HeaderValue::from_static("max-age=31536000"),
910 ));
911 }
912
913 if let Some(listener) = redirect_listener {
914 let api_domain = api_domain.clone();
915
916 let request_id_header = HeaderName::from_static(REQUEST_ID_HEADER);
917
918 let local_addr = listener.local_addr()?;
919 let state_clone = state.clone();
920
921 tokio::spawn(async move {
922 use axum::extract::State;
923
924 let span = server_span_clone3;
925
926 span.in_scope(
927 || tracing::info!(server = %"redirect", addr = %local_addr, "listening"),
928 );
929
930 let handler = async move |uri: Uri,
931 headers: HeaderMap,
932 State(state): State<Arc<OrdinaryApiServerState>>|
933 -> Result<Redirect, StatusCode> {
934 if let Some(host) = get_host(&headers, &uri) {
937 if host == api_domain
938 && let Some(path_and_query) = uri.path_and_query()
939 {
940 return Ok(Redirect::permanent(&format!(
941 "https://{api_domain}{}",
942 path_and_query.as_str()
943 )));
944 }
945
946 let apps = state.apps.read();
947
948 if apps.contains_key(&host)
949 && let Some(path_and_query) = uri.path_and_query()
950 {
951 return Ok(Redirect::permanent(&format!(
952 "https://{host}{}",
953 path_and_query.as_str()
954 )));
955 }
956 }
957
958 Err(StatusCode::NOT_FOUND)
959 };
960
961 let service = redirect_service(
962 span,
963 redacted_hash_clone1,
964 log_ips,
965 log_headers,
966 request_id_header,
967 handler,
968 state_clone,
969 );
970
971 if let Err(err) = axum::serve(
972 listener,
973 service.into_make_service_with_connect_info::<SocketAddr>(),
974 )
975 .with_graceful_shutdown(shutdown_signal())
976 .await
977 {
978 tracing::error!(%err, "server closed");
979 }
980 });
981 }
982
983 let local_addr = listener.local_addr()?;
984
985 let span = server_span_clone2;
986
987 self.start_apps(&span, &state)?;
988
989 match mode {
990 SecurityMode::Insecure => {
991 span.in_scope(|| {
992 tracing::info!(
993 server = %"primary",
994 addr = %local_addr,
995 provision = %"none",
996 "listening"
997 );
998 });
999
1000 axum::serve(
1001 listener,
1002 service.into_make_service_with_connect_info::<SocketAddr>(),
1003 )
1004 .with_graceful_shutdown(signal())
1005 .await?;
1006 }
1007 SecurityMode::Secure(cert_dir_path, provision) => {
1008 let span_clone = span.clone();
1009
1010 let (signal_tx, signal_rx) = watch::channel(());
1011 let (close_tx, close_rx) = watch::channel(());
1012
1013 {
1014 let mut signal_tx_lock = state.signal_tx.write();
1015 *signal_tx_lock = Some(signal_tx.clone());
1016
1017 let mut close_rx_lock = state.close_rx.write();
1018 *close_rx_lock = Some(close_rx.clone());
1019 }
1020
1021 let signal_tx_clone = state.signal_tx.clone();
1022
1023 tokio::spawn(async move {
1024 signal().await;
1025
1026 span_clone.in_scope(|| {
1027 tracing::warn!("graceful shutdown");
1028 });
1029
1030 {
1031 let mut signal_tx_lock = signal_tx_clone.write();
1032 *signal_tx_lock = None;
1033 }
1034
1035 drop(signal_rx);
1036 });
1037
1038 match provision {
1039 ProvisionMode::Localhost => {
1040 let cert_dir_path = cert_dir_path.as_ref();
1041
1042 ordinary_utils::generate_self_signed_localhost_certs(cert_dir_path)?;
1043
1044 let rustls_config = rustls_server_config(
1045 cert_dir_path.join("key.pem"),
1046 cert_dir_path.join("crt.pem"),
1047 )?;
1048
1049 let tls_acceptor = TlsAcceptor::from(rustls_config);
1050 span.in_scope(|| {
1051 tracing::info!(
1052 server = %"primary",
1053 addr = %local_addr,
1054 provision = %"localhost",
1055 "listening"
1056 );
1057 });
1058
1059 loop {
1060 let span = span.clone();
1061 let (cnx, addr) = tokio::select! {
1062 conn = listener.accept() => conn?,
1063 () = signal_tx.closed() => {
1064 span.in_scope(|| {
1065 tracing::warn!("not accepting new connections");
1066 });
1067 break;
1068 }
1069 };
1070
1071 let tower_service = service.clone();
1072 let tls_acceptor = tls_acceptor.clone();
1073
1074 let signal_tx = signal_tx.clone();
1075 let close_rx = close_rx.clone();
1076
1077 tokio::spawn(async move {
1078 match timeout(Duration::from_secs(3), tls_acceptor.accept(cnx))
1080 .await
1081 {
1082 Ok(stream) => {
1083 let stream = match stream {
1084 Ok(stream) => TokioIo::new(stream),
1085 Err(err) => {
1086 span.in_scope(|| {
1087 if log_ips {
1088 tracing::warn!(ip = %addr.ip(), port = addr.port(), %err, "accept");
1089 } else {
1090 tracing::warn!(%err, "accept");
1091 }
1092 });
1093
1094 return;
1095 }
1096 };
1097
1098 Self::handle_stream(
1099 &span,
1100 tower_service,
1101 addr,
1102 stream,
1103 &signal_tx,
1104 &close_rx,
1105 )
1106 .await;
1107 }
1108 Err(_) => {
1109 span.in_scope(|| {
1110 if log_ips {
1111 tracing::warn!(ip = %addr.ip(), port = addr.port(), "timeout");
1112 } else {
1113 tracing::warn!("timeout");
1114 }
1115 });
1116 }
1117 }
1118 });
1119 }
1120 }
1121 ProvisionMode::Staging | ProvisionMode::Production => {
1122 let contacts = api_contacts
1123 .iter()
1124 .map(|c| format!("mailto:{c}"))
1125 .collect::<Vec<String>>();
1126
1127 let cache_path = Path::new(cert_dir_path.as_ref()).join("cache");
1128
1129 let state = AcmeConfig::new([&api_domain])
1130 .contact(contacts)
1131 .cache_option(Some(DirCache::new(cache_path)))
1132 .directory_lets_encrypt(provision == ProvisionMode::Production)
1133 .state();
1134
1135 let challenge_rustls_config = state.challenge_rustls_config();
1136 let mut default_rustls_config =
1137 ServerConfig::builder_with_provider(Arc::new(ring::default_provider()))
1138 .with_safe_default_protocol_versions()?
1139 .with_no_client_auth()
1140 .with_cert_resolver(state.resolver());
1141
1142 default_rustls_config.alpn_protocols =
1143 vec![b"h2".to_vec(), b"http/1.1".to_vec()];
1144
1145 let default_rustls_config = Arc::new(default_rustls_config);
1146
1147 let api_domain_clone = api_domain.clone();
1148 let span_clone = span.clone();
1149
1150 let provision_str = match provision {
1151 ProvisionMode::Production => "production",
1152 ProvisionMode::Staging => "staging",
1153 _ => unreachable!(),
1154 };
1155
1156 let acme_span = span_clone.in_scope(|| {
1157 tracing::info_span!(
1158 "acme",
1159 domain = %api_domain_clone,
1160 provision = %provision_str
1161 )
1162 });
1163
1164 ordinary_utils::acme_task(acme_span.clone(), state, signal_tx.clone());
1165
1166 span.in_scope(|| {
1167 tracing::info!(
1168 server = %"primary",
1169 addr = %local_addr,
1170 provision = %provision_str,
1171 "listening"
1172 );
1173 });
1174
1175 let api_domain = Arc::new(api_domain);
1176
1177 loop {
1178 let span = span.clone();
1179 let (cnx, addr) = tokio::select! {
1180 conn = listener.accept() => conn?,
1181 () = signal_tx.closed() => {
1182 span.in_scope(|| {
1183 tracing::warn!("not accepting new connections");
1184 });
1185 break;
1186 }
1187 };
1188
1189 let tower_service = service.clone();
1190 let acme_span = acme_span.clone();
1191
1192 let challenge_rustls_config = challenge_rustls_config.clone();
1193 let default_rustls_config = default_rustls_config.clone();
1194
1195 let api_domain = api_domain.clone();
1196 let apps = self.apps.clone();
1197
1198 let signal_tx = signal_tx.clone();
1199 let close_rx = close_rx.clone();
1200
1201 tokio::spawn(async move {
1202 match timeout(
1204 Duration::from_secs(3),
1205 LazyConfigAcceptor::new(Acceptor::default(), cnx),
1206 )
1207 .await
1208 {
1209 Ok(start_handshake) => {
1210 let start_handshake = match start_handshake {
1211 Ok(start_handshake) => start_handshake,
1212 Err(err) => {
1213 if log_ips {
1214 let tls_span = span.in_scope(|| { tracing::error_span!("tls", ip = %addr.ip(), port = addr.port()) });
1215
1216 tls_span.in_scope(
1217 || tracing::error!(%err, "accept"),
1218 );
1219 } else {
1220 let tls_span = span
1221 .in_scope(|| tracing::error_span!("tls"));
1222
1223 tls_span.in_scope(
1224 || tracing::error!(%err, "accept"),
1225 );
1226 }
1227
1228 return;
1229 }
1230 };
1231
1232 if let Some(sni) =
1233 start_handshake.client_hello().server_name()
1234 {
1235 let tls_span = span.in_scope(|| {
1236 if log_ips {
1237 tracing::info_span!("tls", %sni, ip = %addr.ip(), port = addr.port())
1238 } else {
1239 tracing::info_span!("tls", %sni)
1240 }
1241 });
1242
1243 if sni == *api_domain {
1244 if let Err(err) = Self::handle_handshake(
1245 &tls_span,
1246 &acme_span,
1247 start_handshake,
1248 tower_service,
1249 addr,
1250 challenge_rustls_config,
1251 default_rustls_config,
1252 &signal_tx,
1253 &close_rx,
1254 )
1255 .await
1256 {
1257 tls_span.in_scope(|| {
1258 tracing::error!(%err, "handshake");
1259 });
1260 }
1261 } else {
1262 let apps = apps.read();
1263
1264 if let Some(app) = apps.get(sni) {
1265 if let Err(err) = app.stream_tx.send((
1266 start_handshake,
1267 addr,
1268 tls_span.clone(),
1269 )) {
1270 tls_span.in_scope(
1271 || tracing::error!(%err, "handoff"),
1272 );
1273 }
1274 } else {
1275 tls_span
1276 .in_scope(|| tracing::warn!("no match"));
1277 }
1278 }
1279 } else {
1280 let tls_span = span.in_scope(|| {
1281 if log_ips {
1282 tracing::info_span!("tls", ip = %addr.ip(), port = addr.port())
1283 } else {
1284 tracing::info_span!("tls")
1285 }
1286 });
1287
1288 tls_span.in_scope(|| tracing::warn!("no sni"));
1289 }
1290 }
1291 Err(_) => {
1292 if log_ips {
1293 let tls_span = span.in_scope(|| { tracing::error_span!("tls", ip = %addr.ip(), port = addr.port()) });
1294
1295 tls_span.in_scope(|| tracing::error!("timeout"));
1296 } else {
1297 let tls_span =
1298 span.in_scope(|| tracing::error_span!("tls"));
1299
1300 tls_span.in_scope(|| tracing::error!("timeout"));
1301 }
1302 }
1303 }
1304 });
1305 }
1306 }
1307 }
1308
1309 {
1310 let mut close_rx_lock = state.close_rx.write();
1311 *close_rx_lock = None;
1312 }
1313
1314 drop(close_rx);
1315 drop(listener);
1316
1317 span.in_scope(|| {
1318 tracing::warn!(
1319 "waiting for {} task(s) to finish",
1320 close_tx.receiver_count()
1321 );
1322 });
1323 close_tx.closed().await;
1324 }
1325 }
1326
1327 Ok(())
1328 }
1329
1330 async fn handle_stream(
1331 span: &Span,
1332 tower_service: Router,
1333 addr: SocketAddr,
1334 stream: TokioIo<TlsStream<TcpStream>>,
1335 signal_tx: &Sender<()>,
1336 close_rx: &Receiver<()>,
1337 ) {
1338 let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| {
1339 let mut tower_service = Extension(ConnectInfo(addr)).layer(tower_service.clone());
1340 tower_service.call(request)
1341 });
1342
1343 let mut builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new());
1344 builder.http2().enable_connect_protocol();
1345
1346 let signal_tx = signal_tx.clone();
1347 let close_rx = close_rx.clone();
1348
1349 let mut conn = pin!(builder.serve_connection_with_upgrades(stream, hyper_service));
1350 let mut signal_closed = pin!(signal_tx.closed().fuse());
1351
1352 loop {
1353 tokio::select! {
1354 result = conn.as_mut() => {
1355 span.in_scope(|| {
1356 match result {
1357 Ok(()) => span.in_scope(|| tracing::info!("closed")),
1358 Err(err) => span.in_scope(|| tracing::warn!(%err, "connection")),
1359 }
1360 });
1361
1362 break;
1363 }
1364 () = &mut signal_closed => {
1365 span.in_scope(|| {
1366 tracing::warn!("shutdown");
1367 });
1368 conn.as_mut().graceful_shutdown();
1369 }
1370 }
1371 }
1372
1373 drop(close_rx);
1374 }
1375
1376 #[allow(clippy::too_many_arguments)]
1377 pub async fn handle_handshake(
1378 tls_span: &Span,
1379 acme_span: &Span,
1380 start_handshake: StartHandshake<TcpStream>,
1381 tower_service: Router,
1382 addr: SocketAddr,
1383 challenge_rustls_config: Arc<ServerConfig>,
1384 default_rustls_config: Arc<ServerConfig>,
1385 signal_tx: &Sender<()>,
1386 close_rx: &Receiver<()>,
1387 ) -> Result<(), Box<dyn Error + Send + Sync>> {
1388 if is_tls_alpn_challenge(&start_handshake.client_hello()) {
1389 acme_span.in_scope(|| tracing::info!(evt = %"validation", "TLS-ALPN-01"));
1390 let mut tls = start_handshake.into_stream(challenge_rustls_config).await?;
1391
1392 tls.shutdown().await?;
1393 } else {
1394 tls_span.in_scope(|| tracing::info!("connecting"));
1395 let as_stream = start_handshake.into_stream(default_rustls_config).await?;
1396
1397 let stream = TokioIo::new(as_stream);
1398
1399 Self::handle_stream(tls_span, tower_service, addr, stream, signal_tx, close_rx).await;
1400 }
1401
1402 Ok(())
1403 }
1404
1405 fn start_apps(
1406 &self,
1407 server_span: &Span,
1408 state: &Arc<OrdinaryApiServerState>,
1409 ) -> Result<(), Box<dyn Error>> {
1410 let state_clone = state.clone();
1411
1412 let Ok(shared_rt) = tokio::runtime::Handle::try_current() else {
1413 return Err("failed to get shared runtime".into());
1414 };
1415
1416 server_span.in_scope(|| -> Result<(), Box<dyn Error>> {
1417 let mut apps = state_clone.apps.write();
1420
1421 for entry in fs::read_dir(&self.apps_dir)?.flatten() {
1422 let config_path = entry.path().join("ordinary.json");
1423
1424 match fs::read_to_string(&config_path) {
1425 Ok(config_str) => match serde_json::from_str::<OrdinaryConfig>(&config_str) {
1426 Ok(config) => {
1427 let domain = config.domain.clone();
1428 let app_span = tracing::info_span!("app", %domain);
1429
1430 match ops::app::dbs(&state_clone, &config, &entry.path(), Some(app_span.clone())) {
1431 Ok((auth, storage)) => {
1432 match ops::app::start(
1433 &state_clone,
1434 &config,
1435 auth,
1436 storage,
1437 match state_clone
1438 .provision_mode
1439 .as_ref()
1440 .unwrap_or(&ProvisionMode::Localhost)
1441 {
1442 ProvisionMode::Localhost => {
1443 ordinary_app::server::ProvisionMode::Localhost
1444 }
1445 ProvisionMode::Staging => {
1446 ordinary_app::server::ProvisionMode::Staging
1447 }
1448 ProvisionMode::Production => {
1449 ordinary_app::server::ProvisionMode::Production
1450 }
1451 },
1452 entry.path().join("certs"),
1453 shared_rt.clone(),
1454 Some(app_span),
1455 ) {
1456 Ok((port, app, kill_tx, stream_tx)) => {
1457 let cnames = app.config.cnames.clone();
1458
1459 let app = Arc::new(WrappedOrdinaryAppServer {
1460 port,
1461 app,
1462 kill_tx,
1463 stream_tx,
1464 });
1465
1466 if let Some(cnames) = cnames {
1467 for cname in cnames {
1468 apps.insert(cname, app.clone());
1469 }
1470 }
1471
1472 apps.insert(
1473 domain,
1474 app,
1475 );
1476 }
1477 Err(err) => {
1478 tracing::error!(%err, domain, "failed to start app");
1479 }
1480 }
1481 }
1482 Err(err) => {
1483 tracing::error!(%err, domain, "failed to init dbs");
1484 }
1485 }
1486 }
1487 Err(err) => {
1488 if let Some(path_str) = config_path.to_str() {
1489 tracing::error!(%err, path = path_str, "failed to parse config file");
1490 } else {
1491 tracing::error!(%err, "failed to parse config file");
1492 }
1493 }
1494 },
1495 Err(err) => {
1496 if let Some(path_str) = config_path.to_str() {
1497 tracing::error!(%err, path = path_str, "failed to read config file");
1498 } else {
1499 tracing::error!(%err, "failed to read config file");
1500 }
1501 }
1502 }
1503 }
1504
1505 drop(apps);
1506
1507 Ok(())
1508 })
1509 }
1510}