diff --git a/Makefile b/Makefile index 0096d1615..61daa2f33 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-reg tunnel-deb: results/$(TUNNEL_BASENAME).deb -results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS) +results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS) build/lib/scripts/forward-port PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables,conntrack ./build/os-compat/run-compat.sh ./dpkg-build.sh $(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE) diff --git a/build/lib/scripts/forward-port b/build/lib/scripts/forward-port index d5fef3931..ec669bc02 100755 --- a/build/lib/scripts/forward-port +++ b/build/lib/scripts/forward-port @@ -5,29 +5,47 @@ if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then exit 1 fi -rule_exists() { - iptables -t nat -C "$@" 2>/dev/null -} +NAME="F$(echo "$sip:$sport -> $dip:$dport" | sha256sum | head -c 15)" -apply_rule() { - if [ "$UNDO" = "1" ]; then - if rule_exists "$@"; then - iptables -t nat -D "$@" - fi - else - if ! rule_exists "$@"; then - iptables -t nat -A "$@" - fi +for kind in INPUT FORWARD ACCEPT; do + if ! iptables -C $kind -j "${NAME}_${kind}" 2> /dev/null; then + iptables -N "${NAME}_${kind}" 2> /dev/null + iptables -A $kind -j "${NAME}_${kind}" fi -} +done +for kind in PREROUTING INPUT OUTPUT POSTROUTING; do + if ! iptables -t nat -C $kind -j "${NAME}_${kind}" 2> /dev/null; then + iptables -t nat -N "${NAME}_${kind}" 2> /dev/null + iptables -t nat -A $kind -j "${NAME}_${kind}" + fi +done -apply_rule PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport -apply_rule PREROUTING -p udp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport -apply_rule OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport -apply_rule OUTPUT -p udp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport -apply_rule POSTROUTING -p tcp -d $dip --dport $dport -j MASQUERADE -apply_rule POSTROUTING -p udp -d $dip --dport $dport -j MASQUERADE +err=0 +trap 'err=1' ERR +for kind in INPUT FORWARD ACCEPT; do + iptables -F "${NAME}_${kind}" 2> /dev/null +done +for kind in PREROUTING INPUT OUTPUT POSTROUTING; do + iptables -t nat -F "${NAME}_${kind}" 2> /dev/null +done if [ "$UNDO" = 1 ]; then conntrack -D -p tcp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active -fi \ No newline at end of file + conntrack -D -p udp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active + exit $err +fi + +iptables -t nat -A ${NAME}_PREROUTING -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport" +iptables -t nat -A ${NAME}_PREROUTING -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport" +iptables -t nat -A ${NAME}_OUTPUT -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport" +iptables -t nat -A ${NAME}_OUTPUT -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport" + +iptables -t nat -A ${NAME}_PREROUTING -s "$dip/24" -d "$sip" -p tcp --dport "$sport" -j DNAT --to-destination "$dip:$dport" +iptables -t nat -A ${NAME}_PREROUTING -s "$dip/24" -d "$sip" -p udp --dport "$sport" -j DNAT --to-destination "$dip:$dport" +iptables -t nat -A ${NAME}_POSTROUTING -s "$dip/24" -d "$dip" -p tcp --dport "$dport" -j MASQUERADE +iptables -t nat -A ${NAME}_POSTROUTING -s "$dip/24" -d "$dip" -p udp --dport "$dport" -j MASQUERADE + +iptables -A ${NAME}_FORWARD -d $dip -p tcp --dport $dport -m state --state NEW -j ACCEPT +iptables -A ${NAME}_FORWARD -d $dip -p udp --dport $dport -m state --state NEW -j ACCEPT + +exit $err \ No newline at end of file diff --git a/container-runtime/container-runtime.service b/container-runtime/container-runtime.service index 2f4b03497..ed9d142f7 100644 --- a/container-runtime/container-runtime.service +++ b/container-runtime/container-runtime.service @@ -4,6 +4,7 @@ OnFailure=container-runtime-failure.service [Service] Type=simple +Environment=RUST_LOG=startos=debug ExecStart=/usr/bin/node --experimental-detect-module --trace-warnings --unhandled-rejections=warn /usr/lib/startos/init/index.js Restart=no diff --git a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts index 1d7e8fc97..837946ca0 100644 --- a/container-runtime/src/Adapters/Systems/SystemForStartOs.ts +++ b/container-runtime/src/Adapters/Systems/SystemForStartOs.ts @@ -68,7 +68,10 @@ export class SystemForStartOs implements System { try { if (this.runningMain || this.starting) return this.starting = true - effects.constRetry = utils.once(() => effects.restart()) + effects.constRetry = utils.once(() => { + console.debug(".const() triggered") + effects.restart() + }) let mainOnTerm: () => Promise | undefined const daemons = await ( await this.abi.main({ diff --git a/core/Cargo.lock b/core/Cargo.lock index d0b6f1426..d9e378165 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -5290,7 +5290,7 @@ dependencies = [ "nix 0.30.1", "patch-db-macro", "serde", - "serde_cbor", + "serde_cbor 0.11.1", "thiserror 2.0.17", "tokio", "tracing", @@ -6477,7 +6477,7 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.3.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?rev=068db90#068db905ee38a7da97cc4a43b806409204e73723" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git#81d18147fd0ca9725b820c010c006e8a2cada322" dependencies = [ "async-stream", "async-trait", @@ -6494,6 +6494,7 @@ dependencies = [ "pin-project", "reqwest", "serde", + "serde_cbor 0.11.2", "serde_json", "thiserror 2.0.17", "tokio", @@ -6933,6 +6934,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index b5cc4120d..e1689199c 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -212,8 +212,8 @@ reqwest = { version = "0.12.25", features = [ ] } reqwest_cookie_store = "0.9.0" rpassword = "7.2.0" -rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", rev = "068db90" } rust-argon2 = "3.0.0" +rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git" } safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true } semver = { version = "1.0.20", features = ["serde"] } serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/core/startos/src/account.rs b/core/startos/src/account.rs index 87e127d17..d583c95f7 100644 --- a/core/startos/src/account.rs +++ b/core/startos/src/account.rs @@ -7,7 +7,7 @@ use openssl::x509::X509; use crate::db::model::DatabaseModel; use crate::hostname::{Hostname, generate_hostname, generate_id}; -use crate::net::ssl::{generate_key, make_root_cert}; +use crate::net::ssl::{gen_nistp256, make_root_cert}; use crate::net::tor::TorSecretKey; use crate::prelude::*; use crate::util::serde::Pem; @@ -37,7 +37,7 @@ impl AccountInfo { let server_id = generate_id(); let hostname = generate_hostname(); let tor_key = vec![TorSecretKey::generate()]; - let root_ca_key = generate_key()?; + let root_ca_key = gen_nistp256()?; let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?; let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random( &mut ssh_key::rand_core::OsRng::default(), @@ -128,7 +128,7 @@ impl AccountInfo { cert_store .as_root_cert_mut() .ser(Pem::new_ref(&self.root_ca_cert))?; - let int_key = crate::net::ssl::generate_key()?; + let int_key = crate::net::ssl::gen_nistp256()?; let int_cert = crate::net::ssl::make_int_cert((&self.root_ca_key, &self.root_ca_cert), &int_key)?; cert_store.as_int_key_mut().ser(&Pem(int_key))?; diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index d9b5ba2d8..50e610e7c 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -1,14 +1,13 @@ use std::fmt; use clap::{CommandFactory, FromArgMatches, Parser}; -pub use crate::ActionId; -use crate::{PackageId, ReplayId}; use qrcode::QrCode; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; +pub use crate::ActionId; use crate::context::{CliContext, RpcContext}; use crate::db::model::package::TaskSeverity; use crate::prelude::*; @@ -16,6 +15,7 @@ use crate::rpc_continuations::Guid; use crate::util::serde::{ HandlerExtSerde, StdinDeserializable, WithIoFormat, display_serializable, }; +use crate::{PackageId, ReplayId}; pub fn action_api() -> ParentHandler { ParentHandler::new() diff --git a/core/startos/src/auth.rs b/core/startos/src/auth.rs index c4ef42475..5d8b71b6c 100644 --- a/core/startos/src/auth.rs +++ b/core/startos/src/auth.rs @@ -14,8 +14,8 @@ use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::middleware::auth::{ - AsLogoutSessionId, AuthContext, HasLoggedOutSessions, HashSessionToken, LoginRes, +use crate::middleware::auth::session::{ + AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes, SessionAuthContext, }; use crate::prelude::*; use crate::util::crypto::EncryptedWire; @@ -110,7 +110,7 @@ impl std::str::FromStr for PasswordType { }) } } -pub fn auth() -> ParentHandler +pub fn auth() -> ParentHandler where CliContext: CallRemote, { @@ -173,7 +173,7 @@ fn gen_pwd() { } #[instrument(skip_all)] -async fn cli_login( +async fn cli_login( HandlerArgs { context: ctx, parent_method, @@ -227,7 +227,7 @@ pub struct LoginParams { } #[instrument(skip_all)] -pub async fn login_impl( +pub async fn login_impl( ctx: C, LoginParams { password, @@ -283,7 +283,7 @@ pub struct LogoutParams { session: InternedString, } -pub async fn logout( +pub async fn logout( ctx: C, LogoutParams { session }: LogoutParams, ) -> Result, Error> { @@ -312,7 +312,7 @@ pub struct SessionList { sessions: Sessions, } -pub fn session() -> ParentHandler +pub fn session() -> ParentHandler where CliContext: CallRemote, { @@ -379,7 +379,7 @@ pub struct ListParams { // #[command(display(display_sessions))] #[instrument(skip_all)] -pub async fn list( +pub async fn list( ctx: C, ListParams { session, .. }: ListParams, ) -> Result { @@ -418,7 +418,10 @@ pub struct KillParams { } #[instrument(skip_all)] -pub async fn kill(ctx: C, KillParams { ids }: KillParams) -> Result<(), Error> { +pub async fn kill( + ctx: C, + KillParams { ids }: KillParams, +) -> Result<(), Error> { HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?; Ok(()) } diff --git a/core/startos/src/backup/backup_bulk.rs b/core/startos/src/backup/backup_bulk.rs index b2795e0a4..0de39f34c 100644 --- a/core/startos/src/backup/backup_bulk.rs +++ b/core/startos/src/backup/backup_bulk.rs @@ -6,7 +6,6 @@ use chrono::Utc; use clap::Parser; use color_eyre::eyre::eyre; use imbl::OrdSet; -use crate::PackageId; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tracing::instrument; @@ -14,6 +13,7 @@ use ts_rs::TS; use super::PackageBackupReport; use super::target::{BackupTargetId, PackageBackupInfo}; +use crate::PackageId; use crate::backup::os::OsBackup; use crate::backup::{BackupReport, ServerBackupReport}; use crate::context::RpcContext; @@ -22,7 +22,7 @@ use crate::db::model::{Database, DatabaseModel}; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; -use crate::middleware::auth::AuthContext; +use crate::middleware::auth::session::SessionAuthContext; use crate::notifications::{NotificationLevel, notify}; use crate::prelude::*; use crate::util::io::{AtomicFile, dir_copy}; diff --git a/core/startos/src/backup/mod.rs b/core/startos/src/backup/mod.rs index 3ca0b583c..a6e98b2b4 100644 --- a/core/startos/src/backup/mod.rs +++ b/core/startos/src/backup/mod.rs @@ -1,9 +1,9 @@ use std::collections::BTreeMap; -use crate::PackageId; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; +use crate::PackageId; use crate::context::CliContext; #[allow(unused_imports)] use crate::prelude::*; diff --git a/core/startos/src/backup/restore.rs b/core/startos/src/backup/restore.rs index f06e86d45..daddd2248 100644 --- a/core/startos/src/backup/restore.rs +++ b/core/startos/src/backup/restore.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use clap::Parser; use futures::{StreamExt, stream}; -use crate::PackageId; use patch_db::json_ptr::ROOT; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; @@ -11,7 +10,6 @@ use tracing::instrument; use ts_rs::TS; use super::target::BackupTargetId; -use crate::PLATFORM; use crate::backup::os::OsBackup; use crate::context::setup::SetupResult; use crate::context::{RpcContext, SetupContext}; @@ -27,6 +25,7 @@ use crate::service::service_map::DownloadInstallFuture; use crate::setup::SetupExecuteProgress; use crate::system::sync_kiosk; use crate::util::serde::IoFormat; +use crate::{PLATFORM, PackageId}; #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/backup/target/mod.rs b/core/startos/src/backup/target/mod.rs index eaeee2e7a..616cffd3b 100644 --- a/core/startos/src/backup/target/mod.rs +++ b/core/startos/src/backup/target/mod.rs @@ -9,8 +9,6 @@ use digest::OutputSizeUser; use digest::generic_array::GenericArray; use exver::Version; use imbl_value::InternedString; -use crate::util::FromStrParser; -use crate::PackageId; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use sha2::Sha256; @@ -19,6 +17,7 @@ use tracing::instrument; use ts_rs::TS; use self::cifs::CifsBackupTarget; +use crate::PackageId; use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; use crate::disk::mount::backup::BackupMountGuard; @@ -28,10 +27,10 @@ use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite}; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; use crate::disk::util::PartitionInfo; use crate::prelude::*; -use crate::util::VersionString; use crate::util::serde::{ HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, serialize_display, }; +use crate::util::{FromStrParser, VersionString}; pub mod cifs; diff --git a/core/startos/src/bins/tunnel.rs b/core/startos/src/bins/tunnel.rs index 0687493ac..5c018f796 100644 --- a/core/startos/src/bins/tunnel.rs +++ b/core/startos/src/bins/tunnel.rs @@ -5,7 +5,6 @@ use std::time::Duration; use clap::Parser; use futures::FutureExt; -use crate::util::future::NonDetachingJoinHandle; use rpc_toolkit::CliApp; use tokio::signal::unix::signal; use tracing::instrument; @@ -20,6 +19,7 @@ use crate::prelude::*; use crate::tunnel::context::{TunnelConfig, TunnelContext}; use crate::tunnel::tunnel_router; use crate::tunnel::web::TunnelCertHandler; +use crate::util::future::NonDetachingJoinHandle; use crate::util::logger::LOGGER; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] diff --git a/core/startos/src/context/cli.rs b/core/startos/src/context/cli.rs index 4a5db1b9e..aafbc796e 100644 --- a/core/startos/src/context/cli.rs +++ b/core/startos/src/context/cli.rs @@ -24,7 +24,7 @@ use super::setup::CURRENT_SECRET; use crate::context::config::{ClientConfig, local_config_path}; use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path}; -use crate::middleware::auth::AuthContext; +use crate::middleware::auth::local::LocalAuthContext; use crate::prelude::*; use crate::rpc_continuations::Guid; use crate::util::io::read_file_to_string; @@ -307,7 +307,7 @@ impl CallRemote for CliContext { ) .with_kind(crate::ErrorKind::Network)?; } - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), HeaderMap::new(), @@ -320,7 +320,7 @@ impl CallRemote for CliContext { } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), HeaderMap::new(), @@ -333,7 +333,7 @@ impl CallRemote for CliContext { } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), HeaderMap::new(), @@ -346,7 +346,7 @@ impl CallRemote for CliContext { } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), HeaderMap::new(), @@ -359,7 +359,7 @@ impl CallRemote for CliContext { } impl CallRemote for CliContext { async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), HeaderMap::new(), diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index 4095a9e6c..f8be8af19 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -8,12 +8,10 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; use chrono::{TimeDelta, Utc}; -use crate::util::future::NonDetachingJoinHandle; use imbl::OrdMap; use imbl_value::InternedString; use itertools::Itertools; use josekit::jwk::Jwk; -use crate::{ActionId, PackageId}; use reqwest::{Client, Proxy}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{CallRemote, Context, Empty}; @@ -22,7 +20,6 @@ use tokio::time::Instant; use tracing::instrument; use super::setup::CURRENT_SECRET; -use crate::DATA_DIR; use crate::account::AccountInfo; use crate::auth::Sessions; use crate::context::config::ServerConfig; @@ -45,9 +42,11 @@ use crate::service::ServiceMap; use crate::service::action::update_tasks; use crate::service::effects::callbacks::ServiceCallbacks; use crate::shutdown::Shutdown; +use crate::util::future::NonDetachingJoinHandle; use crate::util::io::delete_file; use crate::util::lshw::LshwDevice; use crate::util::sync::{SyncMutex, SyncRwLock, Watch}; +use crate::{ActionId, DATA_DIR, PackageId}; pub struct RpcContextSeed { is_closed: AtomicBool, diff --git a/core/startos/src/context/setup.rs b/core/startos/src/context/setup.rs index 3c65eeedc..49c87e001 100644 --- a/core/startos/src/context/setup.rs +++ b/core/startos/src/context/setup.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use std::time::Duration; use futures::{Future, StreamExt}; -use crate::util::future::NonDetachingJoinHandle; use imbl_value::InternedString; use josekit::jwk::Jwk; use patch_db::PatchDb; @@ -28,6 +27,7 @@ use crate::progress::FullProgressTracker; use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations}; use crate::setup::SetupProgress; use crate::shutdown::Shutdown; +use crate::util::future::NonDetachingJoinHandle; use crate::util::net::WebSocketExt; lazy_static::lazy_static! { diff --git a/core/startos/src/control.rs b/core/startos/src/control.rs index fe4a3bd30..81a7547b4 100644 --- a/core/startos/src/control.rs +++ b/core/startos/src/control.rs @@ -1,12 +1,11 @@ use clap::Parser; -use crate::PackageId; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; -use crate::Error; use crate::context::RpcContext; use crate::prelude::*; +use crate::{Error, PackageId}; #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index 073a30e14..b93904dfa 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -4,8 +4,6 @@ use std::path::PathBuf; use chrono::{DateTime, Utc}; use exver::VersionRange; use imbl_value::InternedString; -use crate::util::DataUrl; -use crate::{ActionId, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId}; use patch_db::HasModel; use patch_db::json_ptr::JsonPointer; use reqwest::Url; @@ -18,7 +16,9 @@ use crate::prelude::*; use crate::progress::FullProgress; use crate::s9pk::manifest::Manifest; use crate::status::StatusInfo; +use crate::util::DataUrl; use crate::util::serde::{Pem, is_partial_of}; +use crate::{ActionId, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId}; #[derive(Debug, Default, Deserialize, Serialize, TS)] #[ts(export)] diff --git a/core/startos/src/db/model/private.rs b/core/startos/src/db/model/private.rs index 6e7e8fb03..d9d48ff4d 100644 --- a/core/startos/src/db/model/private.rs +++ b/core/startos/src/db/model/private.rs @@ -1,9 +1,9 @@ use std::collections::{BTreeMap, HashSet}; -use crate::PackageId; use patch_db::{HasModel, Value}; use serde::{Deserialize, Serialize}; +use crate::PackageId; use crate::auth::Sessions; use crate::backup::target::cifs::CifsTargets; use crate::net::forward::AvailablePorts; diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index dfc104101..37018cd75 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -9,7 +9,6 @@ use imbl_value::InternedString; use ipnet::IpNet; use isocountry::CountryCode; use itertools::Itertools; -use crate::{GatewayId, PackageId}; use openssl::hash::MessageDigest; use patch_db::{HasModel, Value}; use serde::{Deserialize, Serialize}; @@ -31,7 +30,7 @@ use crate::util::cpupower::Governor; use crate::util::lshw::LshwDevice; use crate::util::serde::MaybeUtf8String; use crate::version::{Current, VersionT}; -use crate::{ARCH, PLATFORM}; +use crate::{ARCH, GatewayId, PLATFORM, PackageId}; pub static DB_UI_SEED_CELL: OnceLock<&'static str> = OnceLock::new(); diff --git a/core/startos/src/dependencies.rs b/core/startos/src/dependencies.rs index f322a298e..3b6f7bd75 100644 --- a/core/startos/src/dependencies.rs +++ b/core/startos/src/dependencies.rs @@ -2,13 +2,12 @@ use std::collections::BTreeMap; use std::path::Path; use imbl_value::InternedString; -use crate::PackageId; use serde::{Deserialize, Serialize}; use ts_rs::TS; -use crate::Error; use crate::prelude::*; use crate::util::PathOrUrl; +use crate::{Error, PackageId}; #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)] #[model = "Model"] diff --git a/core/startos/src/disk/mount/backup.rs b/core/startos/src/disk/mount/backup.rs index d79e303f3..2c89981dc 100644 --- a/core/startos/src/disk/mount/backup.rs +++ b/core/startos/src/disk/mount/backup.rs @@ -2,11 +2,11 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use color_eyre::eyre::eyre; -use crate::PackageId; use tokio::io::AsyncWriteExt; use tracing::instrument; use super::guard::{GenericMountGuard, TmpMountGuard}; +use crate::PackageId; use crate::auth::check_password; use crate::backup::target::BackupInfo; use crate::disk::mount::filesystem::ReadWrite; diff --git a/core/startos/src/disk/mount/filesystem/idmapped.rs b/core/startos/src/disk/mount/filesystem/idmapped.rs index 3e92351b7..0a351b313 100644 --- a/core/startos/src/disk/mount/filesystem/idmapped.rs +++ b/core/startos/src/disk/mount/filesystem/idmapped.rs @@ -8,7 +8,6 @@ use clap::Parser; use clap::builder::ValueParserFactory; use digest::generic_array::GenericArray; use digest::{Digest, OutputSizeUser}; -use crate::util::FromStrParser; use serde::{Deserialize, Serialize}; use sha2::Sha256; use tokio::process::Command; @@ -16,7 +15,7 @@ use ts_rs::TS; use super::FileSystem; use crate::prelude::*; -use crate::util::Invoke; +use crate::util::{FromStrParser, Invoke}; #[derive(Clone, Copy, Debug, Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/disk/mount/guard.rs b/core/startos/src/disk/mount/guard.rs index e806b4150..66539ef2f 100644 --- a/core/startos/src/disk/mount/guard.rs +++ b/core/startos/src/disk/mount/guard.rs @@ -4,14 +4,13 @@ use std::sync::{Arc, Weak}; use futures::Future; use lazy_static::lazy_static; -use crate::ResultExt; use tokio::sync::Mutex; use tracing::instrument; use super::filesystem::{FileSystem, MountType, ReadOnly, ReadWrite}; use super::util::unmount; -use crate::Error; use crate::util::{Invoke, Never}; +use crate::{Error, ResultExt}; pub const TMP_MOUNTPOINT: &'static str = "/media/startos/tmp"; diff --git a/core/startos/src/error.rs b/core/startos/src/error.rs index 279ba1e6b..f8554e378 100644 --- a/core/startos/src/error.rs +++ b/core/startos/src/error.rs @@ -1,7 +1,6 @@ use std::fmt::{Debug, Display}; use axum::http::StatusCode; -use tokio_rustls::rustls; use axum::http::uri::InvalidUri; use color_eyre::eyre::eyre; use num_enum::TryFromPrimitive; @@ -12,6 +11,7 @@ use rpc_toolkit::yajrc::{ }; use serde::{Deserialize, Serialize}; use tokio::task::JoinHandle; +use tokio_rustls::rustls; use ts_rs::TS; use crate::InvalidId; @@ -217,8 +217,9 @@ impl Error { source: E, kind: ErrorKind, ) -> Self { - let debug = (std::any::TypeId::of::() == std::any::TypeId::of::()) - .then(|| eyre!("{source:?}")); + let debug = (std::any::TypeId::of::() + == std::any::TypeId::of::()) + .then(|| eyre!("{source:?}")); Error { source: source.into(), debug, @@ -591,8 +592,9 @@ where fn with_ctx (ErrorKind, D), D: Display>(self, f: F) -> Result { self.map_err(|e| { let (kind, ctx) = f(&e); - let debug = (std::any::TypeId::of::() == std::any::TypeId::of::()) - .then(|| eyre!("{ctx}: {e:?}")); + let debug = (std::any::TypeId::of::() + == std::any::TypeId::of::()) + .then(|| eyre!("{ctx}: {e:?}")); let source = color_eyre::eyre::Error::from(e); let with_ctx = format!("{ctx}: {source}"); let source = source.wrap_err(with_ctx); diff --git a/core/startos/src/id/gateway.rs b/core/startos/src/id/gateway.rs index 215f91a15..46361e5d5 100644 --- a/core/startos/src/id/gateway.rs +++ b/core/startos/src/id/gateway.rs @@ -2,9 +2,9 @@ use std::convert::Infallible; use std::path::Path; use std::str::FromStr; +use imbl_value::InternedString; use serde::{Deserialize, Serialize}; use ts_rs::TS; -use imbl_value::InternedString; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)] #[ts(type = "string")] diff --git a/core/startos/src/id/host.rs b/core/startos/src/id/host.rs index 67b512f08..aea296ab5 100644 --- a/core/startos/src/id/host.rs +++ b/core/startos/src/id/host.rs @@ -1,9 +1,9 @@ use std::path::Path; use std::str::FromStr; +use imbl_value::InternedString; use serde::{Deserialize, Deserializer, Serialize}; use ts_rs::TS; -use imbl_value::InternedString; use crate::{Id, InvalidId}; diff --git a/core/startos/src/id/mod.rs b/core/startos/src/id/mod.rs index 6783b6e84..3517f30fc 100644 --- a/core/startos/src/id/mod.rs +++ b/core/startos/src/id/mod.rs @@ -1,9 +1,9 @@ use std::borrow::Borrow; use std::str::FromStr; +use imbl_value::InternedString; use regex::Regex; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use imbl_value::InternedString; mod action; mod gateway; diff --git a/core/startos/src/id/package.rs b/core/startos/src/id/package.rs index 2dc7e0ae5..e0fff3ece 100644 --- a/core/startos/src/id/package.rs +++ b/core/startos/src/id/package.rs @@ -2,9 +2,9 @@ use std::borrow::Borrow; use std::path::Path; use std::str::FromStr; +use imbl_value::InternedString; use serde::{Deserialize, Serialize, Serializer}; use ts_rs::TS; -use imbl_value::InternedString; use crate::{Id, InvalidId, SYSTEM_ID}; diff --git a/core/startos/src/id/replay.rs b/core/startos/src/id/replay.rs index 2bc169aa5..8e56bff68 100644 --- a/core/startos/src/id/replay.rs +++ b/core/startos/src/id/replay.rs @@ -2,9 +2,9 @@ use std::convert::Infallible; use std::path::Path; use std::str::FromStr; +use imbl_value::InternedString; use serde::{Deserialize, Serialize}; use ts_rs::TS; -use imbl_value::InternedString; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)] #[ts(type = "string")] diff --git a/core/startos/src/id/service_interface.rs b/core/startos/src/id/service_interface.rs index 27a3f7d82..26ec80e6f 100644 --- a/core/startos/src/id/service_interface.rs +++ b/core/startos/src/id/service_interface.rs @@ -5,8 +5,8 @@ use rpc_toolkit::clap::builder::ValueParserFactory; use serde::{Deserialize, Deserializer, Serialize}; use ts_rs::TS; -use crate::util::FromStrParser; use crate::Id; +use crate::util::FromStrParser; #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)] #[ts(export, type = "string")] diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 79d66cd1d..0a42f7291 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -20,7 +20,7 @@ use crate::db::model::Database; use crate::db::model::public::ServerStatus; use crate::developer::OS_DEVELOPER_KEY_PATH; use crate::hostname::Hostname; -use crate::middleware::auth::AuthContext; +use crate::middleware::auth::local::LocalAuthContext; use crate::net::gateway::UpgradableListener; use crate::net::net_controller::{NetController, NetService}; use crate::net::socks::DEFAULT_SOCKS_LISTEN; diff --git a/core/startos/src/install/mod.rs b/core/startos/src/install/mod.rs index 588ae3fb4..64b8ceda7 100644 --- a/core/startos/src/install/mod.rs +++ b/core/startos/src/install/mod.rs @@ -10,7 +10,6 @@ use exver::VersionRange; use futures::StreamExt; use imbl_value::{InternedString, json}; use itertools::Itertools; -use crate::util::{FromStrParser, VersionString}; use reqwest::Url; use reqwest::header::{CONTENT_LENGTH, HeaderMap}; use rpc_toolkit::HandlerArgs; @@ -31,10 +30,10 @@ use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::s9pk::manifest::PackageId; use crate::s9pk::v2::SIG_CONTEXT; use crate::upload::upload; -use crate::util::Never; use crate::util::io::open_file; use crate::util::net::WebSocketExt; use crate::util::tui::choose; +use crate::util::{FromStrParser, Never, VersionString}; pub const PKG_ARCHIVE_DIR: &str = "package-data/archive"; pub const PKG_PUBLIC_DIR: &str = "package-data/public"; diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index 2383305ca..e15198c0e 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -43,8 +43,8 @@ pub mod diagnostic; pub mod disk; pub mod error; pub mod firmware; -pub mod id; pub mod hostname; +pub mod id; pub mod init; pub mod install; pub mod logs; diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index 17199d117..5a2ee4587 100644 --- a/core/startos/src/logs.rs +++ b/core/startos/src/logs.rs @@ -13,8 +13,6 @@ use color_eyre::eyre::eyre; use futures::stream::BoxStream; use futures::{Future, FutureExt, Stream, StreamExt, TryStreamExt}; use itertools::Itertools; -use crate::util::FromStrParser; -use crate::PackageId; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{ CallRemote, Context, Empty, HandlerArgs, HandlerExt, HandlerFor, ParentHandler, from_fn_async, @@ -27,13 +25,14 @@ use tokio_stream::wrappers::LinesStream; use tokio_tungstenite::tungstenite::Message; use tracing::instrument; +use crate::PackageId; use crate::context::{CliContext, RpcContext}; use crate::error::ResultExt; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations}; -use crate::util::Invoke; use crate::util::net::WebSocketExt; use crate::util::serde::Reversible; +use crate::util::{FromStrParser, Invoke}; #[pin_project::pin_project] pub struct LogStream { diff --git a/core/startos/src/lxc/mod.rs b/core/startos/src/lxc/mod.rs index 2b154309e..30734c322 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -7,8 +7,6 @@ use std::time::Duration; use clap::builder::ValueParserFactory; use futures::StreamExt; use imbl_value::InternedString; -use crate::util::FromStrParser; -use crate::{InvalidId, PackageId}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{RpcRequest, RpcResponse}; use serde::{Deserialize, Serialize}; @@ -31,7 +29,8 @@ use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::service::ServiceStats; use crate::util::io::open_file; use crate::util::rpc_client::UnixRpcClient; -use crate::util::{Invoke, new_guid}; +use crate::util::{FromStrParser, Invoke, new_guid}; +use crate::{InvalidId, PackageId}; const LXC_CONTAINER_DIR: &str = "/var/lib/lxc"; const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path diff --git a/core/startos/src/middleware/auth/local.rs b/core/startos/src/middleware/auth/local.rs new file mode 100644 index 000000000..342e1a882 --- /dev/null +++ b/core/startos/src/middleware/auth/local.rs @@ -0,0 +1,101 @@ +use base64::Engine; +use basic_cookies::Cookie; +use http::HeaderValue; +use http::header::COOKIE; +use rand::random; +use rpc_toolkit::yajrc::{RpcError, RpcResponse}; +use rpc_toolkit::{Context, Empty, Middleware}; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; + +use crate::context::RpcContext; +use crate::prelude::*; +use crate::util::Invoke; +use crate::util::io::{create_file_mod, read_file_to_string}; +use crate::util::serde::BASE64; + +pub trait LocalAuthContext: Context { + const LOCAL_AUTH_COOKIE_PATH: &str; + const LOCAL_AUTH_COOKIE_OWNERSHIP: &str; + fn init_auth_cookie() -> impl Future> + Send { + async { + let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o640).await?; + file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes()) + .await?; + file.sync_all().await?; + drop(file); + Command::new("chown") + .arg(Self::LOCAL_AUTH_COOKIE_OWNERSHIP) + .arg(Self::LOCAL_AUTH_COOKIE_PATH) + .invoke(crate::ErrorKind::Filesystem) + .await?; + Ok(()) + } + } +} + +impl LocalAuthContext for RpcContext { + const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie"; + const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos"; +} + +fn unauthorized() -> Error { + Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization) +} + +async fn check_from_header(header: Option<&HeaderValue>) -> Result<(), Error> { + if let Some(cookie_header) = header { + let cookies = Cookie::parse( + cookie_header + .to_str() + .with_kind(crate::ErrorKind::Authorization)?, + ) + .with_kind(crate::ErrorKind::Authorization)?; + if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") { + return check_cookie::(cookie).await; + } + } + Err(unauthorized()) +} + +async fn check_cookie(local: &Cookie<'_>) -> Result<(), Error> { + if let Ok(token) = read_file_to_string(C::LOCAL_AUTH_COOKIE_PATH).await { + if local.get_value() == &*token { + return Ok(()); + } + } + + Err(unauthorized()) +} + +#[derive(Clone)] +pub struct LocalAuth { + cookie: Option, +} +impl LocalAuth { + pub fn new() -> Self { + Self { cookie: None } + } +} + +impl Middleware for LocalAuth { + type Metadata = Empty; + async fn process_http_request( + &mut self, + _: &C, + request: &mut axum::extract::Request, + ) -> Result<(), axum::response::Response> { + self.cookie = request.headers().get(COOKIE).cloned(); + Ok(()) + } + async fn process_rpc_request( + &mut self, + _: &C, + _: Self::Metadata, + _: &mut rpc_toolkit::RpcRequest, + ) -> Result<(), rpc_toolkit::RpcResponse> { + check_from_header::(self.cookie.as_ref()) + .await + .map_err(|e| RpcResponse::from(RpcError::from(e))) + } +} diff --git a/core/startos/src/middleware/auth/mod.rs b/core/startos/src/middleware/auth/mod.rs new file mode 100644 index 000000000..3cee14769 --- /dev/null +++ b/core/startos/src/middleware/auth/mod.rs @@ -0,0 +1,113 @@ +use axum::extract::Request; +use axum::response::Response; +use rpc_toolkit::{Context, DynMiddleware, Middleware, RpcRequest, RpcResponse}; +use serde::Deserialize; + +use crate::context::RpcContext; +use crate::db::model::Database; +use crate::middleware::auth::local::{LocalAuth, LocalAuthContext}; +use crate::middleware::auth::session::{SessionAuth, SessionAuthContext}; +use crate::middleware::auth::signature::{SignatureAuth, SignatureAuthContext}; +use crate::prelude::*; +use crate::util::serde::const_true; + +pub mod local; +pub mod session; +pub mod signature; + +pub trait DbContext: Context { + type Database: HasModel> + Send + Sync; + fn db(&self) -> &TypedPatchDb; +} +impl DbContext for RpcContext { + type Database = Database; + fn db(&self) -> &TypedPatchDb { + &self.db + } +} + +#[derive(Deserialize)] +pub struct Metadata { + #[serde(default = "const_true")] + authenticated: bool, +} + +pub struct Auth(Vec>); +impl Clone for Auth { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} +impl Auth { + pub fn new() -> Self { + Self(Vec::new()) + } +} +impl Auth { + pub fn with_local_auth(mut self) -> Self { + self.0.push(DynMiddleware::new(LocalAuth::new())); + self + } +} +impl Auth { + pub fn with_signature_auth(mut self) -> Self { + self.0.push(DynMiddleware::new(SignatureAuth::new())); + self + } +} +impl Auth { + pub fn with_session_auth(mut self) -> Self { + self.0.push(DynMiddleware::new(SessionAuth::new())); + self + } +} +impl Middleware for Auth { + type Metadata = Value; + async fn process_http_request( + &mut self, + context: &C, + request: &mut Request, + ) -> Result<(), Response> { + for middleware in self.0.iter_mut() { + middleware.process_http_request(context, request).await?; + } + Ok(()) + } + async fn process_rpc_request( + &mut self, + context: &C, + metadata: Self::Metadata, + request: &mut RpcRequest, + ) -> Result<(), RpcResponse> { + let m: Metadata = + from_value(metadata.clone()).map_err(|e| RpcResponse::from_result(Err(e)))?; + let mut err = None; + for middleware in self.0.iter_mut() { + if let Err(e) = middleware + .process_rpc_request(context, metadata.clone(), request) + .await + { + if m.authenticated { + err = Some(e); + } + } else { + return Ok(()); + } + } + if let Some(e) = err { + return Err(e); + } + + Ok(()) + } + async fn process_rpc_response(&mut self, context: &C, response: &mut RpcResponse) { + for middleware in self.0.iter_mut() { + middleware.process_rpc_response(context, response).await; + } + } + async fn process_http_response(&mut self, context: &C, response: &mut Response) { + for middleware in self.0.iter_mut() { + middleware.process_http_response(context, response).await; + } + } +} diff --git a/core/startos/src/middleware/auth.rs b/core/startos/src/middleware/auth/session.rs similarity index 68% rename from core/startos/src/middleware/auth.rs rename to core/startos/src/middleware/auth/session.rs index d746bb1ee..375351ce8 100644 --- a/core/startos/src/middleware/auth.rs +++ b/core/startos/src/middleware/auth/session.rs @@ -1,32 +1,23 @@ use std::borrow::Borrow; use std::collections::BTreeSet; -use std::future::Future; use std::ops::Deref; use std::sync::Arc; use std::time::{Duration, Instant}; use axum::extract::Request; use axum::response::Response; -use base64::Engine; use basic_cookies::Cookie; use chrono::Utc; -use color_eyre::eyre::eyre; -use digest::Digest; use http::HeaderValue; use http::header::{COOKIE, USER_AGENT}; -use imbl_value::{InternedString, json}; -use rand::random; use rpc_toolkit::yajrc::INTERNAL_ERROR; use rpc_toolkit::{Middleware, RpcRequest, RpcResponse}; use serde::{Deserialize, Serialize}; -use sha2::Sha256; -use tokio::io::AsyncWriteExt; -use tokio::process::Command; -use tokio::sync::Mutex; +use sha2::{Digest, Sha256}; use crate::auth::{Sessions, check_password, write_shadow}; use crate::context::RpcContext; -use crate::middleware::signature::{SignatureAuth, SignatureAuthContext}; +use crate::middleware::auth::DbContext; use crate::prelude::*; use crate::rpc_continuations::OpenAuthedContinuations; use crate::util::Invoke; @@ -34,24 +25,7 @@ use crate::util::io::{create_file_mod, read_file_to_string}; use crate::util::serde::{BASE64, const_true}; use crate::util::sync::SyncMutex; -pub trait AuthContext: SignatureAuthContext { - const LOCAL_AUTH_COOKIE_PATH: &str; - const LOCAL_AUTH_COOKIE_OWNERSHIP: &str; - fn init_auth_cookie() -> impl Future> + Send { - async { - let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o640).await?; - file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes()) - .await?; - file.sync_all().await?; - drop(file); - Command::new("chown") - .arg(Self::LOCAL_AUTH_COOKIE_OWNERSHIP) - .arg(Self::LOCAL_AUTH_COOKIE_PATH) - .invoke(crate::ErrorKind::Filesystem) - .await?; - Ok(()) - } - } +pub trait SessionAuthContext: DbContext { fn ephemeral_sessions(&self) -> &SyncMutex; fn open_authed_continuations(&self) -> &OpenAuthedContinuations>; fn access_sessions(db: &mut Model) -> &mut Model; @@ -62,9 +36,7 @@ pub trait AuthContext: SignatureAuthContext { } } -impl AuthContext for RpcContext { - const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie"; - const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos"; +impl SessionAuthContext for RpcContext { fn ephemeral_sessions(&self) -> &SyncMutex { &self.ephemeral_sessions } @@ -103,7 +75,7 @@ pub trait AsLogoutSessionId { pub struct HasLoggedOutSessions(()); impl HasLoggedOutSessions { - pub async fn new( + pub async fn new( sessions: impl IntoIterator, ctx: &C, ) -> Result { @@ -134,90 +106,6 @@ impl HasLoggedOutSessions { } } -/// Used when we need to know that we have logged in with a valid user -#[derive(Clone)] -pub struct HasValidSession(SessionType); - -#[derive(Clone)] -enum SessionType { - Local, - Session(HashSessionToken), -} - -impl HasValidSession { - pub async fn from_header( - header: Option<&HeaderValue>, - ctx: &C, - ) -> Result { - if let Some(cookie_header) = header { - let cookies = Cookie::parse( - cookie_header - .to_str() - .with_kind(crate::ErrorKind::Authorization)?, - ) - .with_kind(crate::ErrorKind::Authorization)?; - if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") { - if let Ok(s) = Self::from_local::(cookie).await { - return Ok(s); - } - } - if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "session") { - if let Ok(s) = Self::from_session(HashSessionToken::from_cookie(cookie), ctx).await - { - return Ok(s); - } - } - } - Err(Error::new( - eyre!("UNAUTHORIZED"), - crate::ErrorKind::Authorization, - )) - } - - pub async fn from_session( - session_token: HashSessionToken, - ctx: &C, - ) -> Result { - let session_hash = session_token.hashed(); - if !ctx.ephemeral_sessions().mutate(|s| { - if let Some(session) = s.0.get_mut(session_hash) { - session.last_active = Utc::now(); - true - } else { - false - } - }) { - ctx.db() - .mutate(|db| { - C::access_sessions(db) - .as_idx_mut(session_hash) - .ok_or_else(|| { - Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization) - })? - .mutate(|s| { - s.last_active = Utc::now(); - Ok(()) - }) - }) - .await - .result?; - } - Ok(Self(SessionType::Session(session_token))) - } - - pub async fn from_local(local: &Cookie<'_>) -> Result { - let token = read_file_to_string(C::LOCAL_AUTH_COOKIE_PATH).await?; - if local.get_value() == &*token { - Ok(Self(SessionType::Local)) - } else { - Err(Error::new( - eyre!("UNAUTHORIZED"), - crate::ErrorKind::Authorization, - )) - } - } -} - /// When we have a need to create a new session, /// Or when we are using internal valid authenticated service. #[derive(Debug, Clone)] @@ -312,51 +200,97 @@ impl Borrow for HashSessionToken { } } +pub struct ValidSessionToken(pub HashSessionToken); +impl ValidSessionToken { + pub async fn from_header( + header: Option<&HeaderValue>, + ctx: &C, + ) -> Result { + if let Some(cookie_header) = header { + let cookies = Cookie::parse( + cookie_header + .to_str() + .with_kind(crate::ErrorKind::Authorization)?, + ) + .with_kind(crate::ErrorKind::Authorization)?; + if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "session") { + if let Ok(s) = Self::from_session(HashSessionToken::from_cookie(cookie), ctx).await + { + return Ok(s); + } + } + } + Err(Error::new( + eyre!("UNAUTHORIZED"), + crate::ErrorKind::Authorization, + )) + } + + pub async fn from_session( + session_token: HashSessionToken, + ctx: &C, + ) -> Result { + let session_hash = session_token.hashed(); + if !ctx.ephemeral_sessions().mutate(|s| { + if let Some(session) = s.0.get_mut(session_hash) { + session.last_active = Utc::now(); + true + } else { + false + } + }) { + ctx.db() + .mutate(|db| { + C::access_sessions(db) + .as_idx_mut(session_hash) + .ok_or_else(|| { + Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization) + })? + .mutate(|s| { + s.last_active = Utc::now(); + Ok(()) + }) + }) + .await + .result?; + } + Ok(Self(session_token)) + } +} + #[derive(Deserialize)] pub struct Metadata { - #[serde(default = "const_true")] - authenticated: bool, #[serde(default)] login: bool, #[serde(default)] get_session: bool, - #[serde(default)] - get_signer: bool, } #[derive(Clone)] -pub struct Auth { - rate_limiter: Arc>, - cookie: Option, +pub struct SessionAuth { + rate_limiter: Arc>, is_login: bool, + cookie: Option, set_cookie: Option, user_agent: Option, - signature_auth: SignatureAuth, } -impl Auth { +impl SessionAuth { pub fn new() -> Self { Self { - rate_limiter: Arc::new(Mutex::new((0, Instant::now()))), - cookie: None, + rate_limiter: Arc::new(SyncMutex::new((0, Instant::now()))), is_login: false, + cookie: None, set_cookie: None, user_agent: None, - signature_auth: SignatureAuth::new(), } } } -impl Middleware for Auth { + +impl Middleware for SessionAuth { type Metadata = Metadata; - async fn process_http_request( - &mut self, - context: &C, - request: &mut Request, - ) -> Result<(), Response> { - self.cookie = request.headers_mut().remove(COOKIE); - self.user_agent = request.headers_mut().remove(USER_AGENT); - self.signature_auth - .process_http_request(context, request) - .await?; + async fn process_http_request(&mut self, _: &C, request: &mut Request) -> Result<(), Response> { + self.cookie = request.headers().get(COOKIE).cloned(); + self.user_agent = request.headers().get(USER_AGENT).cloned(); Ok(()) } async fn process_rpc_request( @@ -368,56 +302,37 @@ impl Middleware for Auth { async { if metadata.login { self.is_login = true; - let guard = self.rate_limiter.lock().await; - if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 { - return Err(Error::new( - eyre!("Please limit login attempts to 3 per 20 seconds."), - crate::ErrorKind::RateLimited, - )); - } + self.rate_limiter.mutate(|(count, time)| { + if time.elapsed() < Duration::from_secs(20) && *count >= 3 { + Err(Error::new( + eyre!("Please limit login attempts to 3 per 20 seconds."), + crate::ErrorKind::RateLimited, + )) + } else { + *count += 1; + *time = Instant::now(); + Ok(()) + } + })?; if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) { request.params["__Auth_userAgent"] = Value::String(Arc::new(user_agent.to_owned())) - // TODO: will this panic? } - } else if metadata.authenticated { - if self - .signature_auth - .process_rpc_request( - context, - from_value(json!({ - "get_signer": metadata.get_signer - }))?, - request, - ) - .await - .is_err() - { - match HasValidSession::from_header(self.cookie.as_ref(), context).await? { - HasValidSession(SessionType::Session(s)) if metadata.get_session => { - request.params["__Auth_session"] = - Value::String(Arc::new(s.hashed().deref().to_owned())); - } - _ => (), - } + } else { + let ValidSessionToken(s) = + ValidSessionToken::from_header(self.cookie.as_ref(), context).await?; + if metadata.get_session { + request.params["__Auth_session"] = + Value::String(Arc::new(s.hashed().deref().to_owned())); } } - Ok(()) + Ok::<_, Error>(()) } .await .map_err(|e| RpcResponse::from_result(Err(e))) } async fn process_rpc_response(&mut self, _: &C, response: &mut RpcResponse) { if self.is_login { - let mut guard = self.rate_limiter.lock().await; - if guard.1.elapsed() < Duration::from_secs(20) { - if response.result.is_err() { - guard.0 += 1; - } - } else { - guard.0 = 0; - } - guard.1 = Instant::now(); if response.result.is_ok() { let res = std::mem::replace(&mut response.result, Err(INTERNAL_ERROR)); response.result = async { diff --git a/core/startos/src/middleware/signature.rs b/core/startos/src/middleware/auth/signature.rs similarity index 96% rename from core/startos/src/middleware/signature.rs rename to core/startos/src/middleware/auth/signature.rs index 1ad72197c..996a4d6a5 100644 --- a/core/startos/src/middleware/signature.rs +++ b/core/startos/src/middleware/auth/signature.rs @@ -8,14 +8,14 @@ use axum::extract::Request; use http::{HeaderMap, HeaderValue}; use reqwest::Client; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{Context, Middleware, RpcRequest, RpcResponse}; +use rpc_toolkit::{Middleware, RpcRequest, RpcResponse}; use serde::Deserialize; use serde::de::DeserializeOwned; use tokio::sync::Mutex; use url::Url; use crate::context::{CliContext, RpcContext}; -use crate::db::model::Database; +use crate::middleware::auth::DbContext; use crate::prelude::*; use crate::sign::commitment::Commitment; use crate::sign::commitment::request::RequestCommitment; @@ -25,11 +25,9 @@ use crate::util::serde::Base64; pub const AUTH_SIG_HEADER: &str = "X-StartOS-Auth-Sig"; -pub trait SignatureAuthContext: Context { - type Database: HasModel> + Send + Sync; +pub trait SignatureAuthContext: DbContext { type AdditionalMetadata: DeserializeOwned + Send; type CheckPubkeyRes: Send; - fn db(&self) -> &TypedPatchDb; fn sig_context( &self, ) -> impl Future + Send, Error>> + Send> @@ -47,12 +45,8 @@ pub trait SignatureAuthContext: Context { } impl SignatureAuthContext for RpcContext { - type Database = Database; type AdditionalMetadata = (); type CheckPubkeyRes = (); - fn db(&self) -> &TypedPatchDb { - &self.db - } async fn sig_context( &self, ) -> impl IntoIterator + Send, Error>> + Send { @@ -96,7 +90,7 @@ impl SignatureAuthContext for RpcContext { } Err(Error::new( - eyre!("Developer Key is not authorized"), + eyre!("Key is not authorized"), ErrorKind::IncorrectPassword, )) } diff --git a/core/startos/src/middleware/mod.rs b/core/startos/src/middleware/mod.rs index f71837a93..9180cc208 100644 --- a/core/startos/src/middleware/mod.rs +++ b/core/startos/src/middleware/mod.rs @@ -2,4 +2,3 @@ pub mod auth; pub mod connect_info; pub mod cors; pub mod db; -pub mod signature; diff --git a/core/startos/src/net/acme.rs b/core/startos/src/net/acme.rs index 94f00af20..1d0874c81 100644 --- a/core/startos/src/net/acme.rs +++ b/core/startos/src/net/acme.rs @@ -9,8 +9,6 @@ use clap::builder::ValueParserFactory; use futures::StreamExt; use imbl_value::InternedString; use itertools::Itertools; -use crate::error::ErrorData; -use crate::util::FromStrParser; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; @@ -27,10 +25,12 @@ use crate::context::{CliContext, RpcContext}; use crate::db::model::Database; use crate::db::model::public::AcmeSettings; use crate::db::{DbAccess, DbAccessByKey, DbAccessMut}; +use crate::error::ErrorData; use crate::net::ssl::should_use_cert; use crate::net::tls::{SingleCertResolver, TlsHandler}; use crate::net::web_server::Accept; use crate::prelude::*; +use crate::util::FromStrParser; use crate::util::serde::{Pem, Pkcs8Doc}; use crate::util::sync::{SyncMutex, Watch}; diff --git a/core/startos/src/net/dns.rs b/core/startos/src/net/dns.rs index 2b18cc206..7e0a09d1e 100644 --- a/core/startos/src/net/dns.rs +++ b/core/startos/src/net/dns.rs @@ -9,7 +9,6 @@ use clap::Parser; use color_eyre::eyre::eyre; use futures::future::BoxFuture; use futures::{FutureExt, StreamExt}; -use crate::util::future::NonDetachingJoinHandle; use hickory_client::client::Client; use hickory_client::proto::DnsHandle; use hickory_client::proto::runtime::TokioRuntimeProvider; @@ -23,7 +22,6 @@ use hickory_server::proto::rr::{Name, Record, RecordType}; use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo}; use imbl::OrdMap; use imbl_value::InternedString; -use crate::{GatewayId, OptionExt, PackageId}; use patch_db::json_ptr::JsonPointer; use rpc_toolkit::{ Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async, from_fn_blocking, @@ -38,9 +36,11 @@ use crate::db::model::public::NetworkInterfaceInfo; use crate::net::gateway::NetworkInterfaceWatcher; use crate::prelude::*; use crate::util::actor::background::BackgroundJobQueue; +use crate::util::future::NonDetachingJoinHandle; use crate::util::io::file_string_stream; use crate::util::serde::{HandlerExtSerde, display_serializable}; use crate::util::sync::{SyncRwLock, Watch}; +use crate::{GatewayId, OptionExt, PackageId}; pub fn dns_api() -> ParentHandler { ParentHandler::new() diff --git a/core/startos/src/net/forward.rs b/core/startos/src/net/forward.rs index 9a535fa4b..a9451e4e6 100644 --- a/core/startos/src/net/forward.rs +++ b/core/startos/src/net/forward.rs @@ -4,21 +4,21 @@ use std::sync::{Arc, Weak}; use std::time::Duration; use futures::channel::oneshot; -use crate::util::future::NonDetachingJoinHandle; use id_pool::IdPool; use iddqd::{IdOrdItem, IdOrdMap}; use imbl::OrdMap; -use crate::GatewayId; use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use tokio::sync::mpsc; +use crate::GatewayId; use crate::context::{CliContext, RpcContext}; use crate::db::model::public::NetworkInterfaceInfo; use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter}; use crate::prelude::*; use crate::util::Invoke; +use crate::util::future::NonDetachingJoinHandle; use crate::util::serde::{HandlerExtSerde, display_serializable}; use crate::util::sync::Watch; @@ -180,11 +180,51 @@ pub struct PortForwardController { _thread: NonDetachingJoinHandle<()>, } +pub async fn add_iptables_rule(nat: bool, undo: bool, args: &[&str]) -> Result<(), Error> { + let mut cmd = Command::new("iptables"); + if nat { + cmd.arg("-t").arg("nat"); + } + if undo != !cmd.arg("-C").args(args).status().await?.success() { + let mut cmd = Command::new("iptables"); + if nat { + cmd.arg("-t").arg("nat"); + } + if undo { + cmd.arg("-D"); + } else { + cmd.arg("-A"); + } + cmd.args(args).invoke(ErrorKind::Network).await?; + } + Ok(()) +} + impl PortForwardController { pub fn new() -> Self { let (req_send, mut req_recv) = mpsc::unbounded_channel::(); let thread = NonDetachingJoinHandle::from(tokio::spawn(async move { while let Err(e) = async { + Command::new("iptables") + .arg("-P") + .arg("FORWARD") + .arg("DROP") + .invoke(ErrorKind::Network) + .await?; + add_iptables_rule( + false, + false, + &[ + "FORWARD", + "-m", + "state", + "--state", + "ESTABLISHED,RELATED", + "-j", + "ACCEPT", + ], + ) + .await?; Command::new("sysctl") .arg("-w") .arg("net.ipv4.ip_forward=1") diff --git a/core/startos/src/net/gateway.rs b/core/startos/src/net/gateway.rs index 8bb5297ac..fe90e15df 100644 --- a/core/startos/src/net/gateway.rs +++ b/core/startos/src/net/gateway.rs @@ -10,12 +10,10 @@ use std::time::Duration; use clap::Parser; use futures::future::Either; use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; -use crate::util::future::NonDetachingJoinHandle; use imbl::{OrdMap, OrdSet}; use imbl_value::InternedString; use ipnet::IpNet; use itertools::Itertools; -use crate::GatewayId; use nix::net::if_::if_nametoindex; use patch_db::json_ptr::JsonPointer; use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; @@ -32,6 +30,7 @@ use zbus::zvariant::{ }; use zbus::{Connection, proxy}; +use crate::GatewayId; use crate::context::{CliContext, RpcContext}; use crate::db::model::Database; use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceType}; @@ -42,7 +41,7 @@ use crate::net::web_server::{Accept, AcceptStream, Acceptor, MetadataVisitor}; use crate::prelude::*; use crate::util::Invoke; use crate::util::collections::OrdMapIterMut; -use crate::util::future::Until; +use crate::util::future::{NonDetachingJoinHandle, Until}; use crate::util::io::open_file; use crate::util::serde::{HandlerExtSerde, display_serializable}; use crate::util::sync::{SyncMutex, Watch}; @@ -65,13 +64,15 @@ pub fn gateway_api() -> ParentHandler { for (iface, info) in res { table.add_row(row![ iface, - info.ip_info.as_ref() + info.ip_info + .as_ref() .and_then(|ip_info| ip_info.device_type) .map_or_else(|| "UNKNOWN".to_owned(), |ty| format!("{ty:?}")), info.public(), info.ip_info.as_ref().map_or_else( || "".to_owned(), - |ip_info| ip_info.subnets + |ip_info| ip_info + .subnets .iter() .map(|ipnet| match ipnet.addr() { IpAddr::V4(ip) => format!("{ip}/{}", ipnet.prefix_len()), @@ -81,8 +82,10 @@ pub fn gateway_api() -> ParentHandler { ipnet.prefix_len() ), }) - .join(", ")), - info.ip_info.as_ref() + .join(", ") + ), + info.ip_info + .as_ref() .and_then(|ip_info| ip_info.wan_ip) .map_or_else(|| "N/A".to_owned(), |ip| ip.to_string()) ]); @@ -102,26 +105,34 @@ pub fn gateway_api() -> ParentHandler { .no_display() .with_about("Indicate whether this gateway has inbound access from the WAN") .with_call_remote::(), - ).subcommand( + ) + .subcommand( "unset-public", from_fn_async(unset_public) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_about("Allow this gateway to infer whether it has inbound access from the WAN based on its IPv4 address") + .with_about(concat!( + "Allow this gateway to infer whether it has", + " inbound access from the WAN based on its IPv4 address" + )) .with_call_remote::(), - ).subcommand("forget", + ) + .subcommand( + "forget", from_fn_async(forget_iface) .with_metadata("sync_db", Value::Bool(true)) .no_display() .with_about("Forget a disconnected gateway") - .with_call_remote::() - ).subcommand("set-name", - from_fn_async(set_name) - .with_metadata("sync_db", Value::Bool(true)) - .no_display() - .with_about("Rename a gateway") - .with_call_remote::() - ) + .with_call_remote::(), + ) + .subcommand( + "set-name", + from_fn_async(set_name) + .with_metadata("sync_db", Value::Bool(true)) + .no_display() + .with_about("Rename a gateway") + .with_call_remote::(), + ) } async fn list_interfaces( @@ -266,6 +277,9 @@ trait Ip4Config { #[zbus(property)] fn address_data(&self) -> Result, Error>; + #[zbus(property)] + fn route_data(&self) -> Result, Error>; + #[zbus(property)] fn gateway(&self) -> Result; @@ -301,6 +315,14 @@ impl TryFrom for IpNet { } } +#[derive(Clone, Debug, DeserializeDict, ZValue, ZType)] +#[zvariant(signature = "dict")] +struct RouteData { + dest: String, + prefix: u32, + table: Option, +} + #[derive(Clone, Debug, DeserializeDict, ZValue, ZType)] #[zvariant(signature = "dict")] struct NameserverData { @@ -613,6 +635,7 @@ async fn watch_ip( Ip6ConfigProxy::new(&connection, ip6_config.clone()).await?; let mut until = Until::new() .with_stream(ip4_proxy.receive_address_data_changed().await.stub()) + .with_stream(ip4_proxy.receive_route_data_changed().await.stub()) .with_stream(ip4_proxy.receive_gateway_changed().await.stub()) .with_stream( ip4_proxy.receive_nameserver_data_changed().await.stub(), @@ -680,6 +703,22 @@ async fn watch_ip( .into_iter() .map(IpNet::try_from) .try_collect()?; + let tables = ip4_proxy.route_data().await?.into_iter().filter_map(|d|d.table).collect::>(); + if !tables.is_empty() { + let rules = String::from_utf8(Command::new("ip").arg("rule").arg("list").invoke(ErrorKind::Network).await?)?; + for table in tables { + for subnet in subnets.iter().filter(|s| s.addr().is_ipv4()) { + let subnet_string = subnet.trunc().to_string(); + let rule = ["from", &subnet_string, "lookup", &table.to_string()]; + if !rules.contains(&rule.join(" ")) { + if rules.contains(&rule[..2].join(" ")) { + Command::new("ip").arg("rule").arg("del").args(&rule[..2]).invoke(ErrorKind::Network).await?; + } + Command::new("ip").arg("rule").arg("add").args(rule).invoke(ErrorKind::Network).await?; + } + } + } + } let wan_ip = if !subnets.is_empty() && !matches!( device_type, @@ -1405,12 +1444,8 @@ impl ListenerMap { &mut self, cx: &mut std::task::Context<'_>, ) -> Poll::Metadata, AcceptStream), Error>> { - for (addr, listener) in self.listeners.iter_mut() { - if let Poll::Ready((metadata, stream)) = listener.poll_accept(cx)? { - return Poll::Ready(Ok((*addr, metadata, stream))); - } - } - Poll::Pending + let (metadata, stream) = ready!(self.listeners.poll_accept(cx)?); + Poll::Ready(Ok((metadata.key, metadata.inner, stream))) } } diff --git a/core/startos/src/net/host/address.rs b/core/startos/src/net/host/address.rs index d809705fe..296b8a934 100644 --- a/core/startos/src/net/host/address.rs +++ b/core/startos/src/net/host/address.rs @@ -3,11 +3,11 @@ use std::net::Ipv4Addr; use clap::Parser; use imbl_value::InternedString; -use crate::GatewayId; use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::GatewayId; use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; use crate::net::acme::AcmeProvider; diff --git a/core/startos/src/net/host/binding.rs b/core/startos/src/net/host/binding.rs index 805562a85..a74d351aa 100644 --- a/core/startos/src/net/host/binding.rs +++ b/core/startos/src/net/host/binding.rs @@ -4,8 +4,6 @@ use std::str::FromStr; use clap::Parser; use clap::builder::ValueParserFactory; use imbl::OrdSet; -use crate::util::FromStrParser; -use crate::{GatewayId, HostId}; use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -17,7 +15,9 @@ use crate::net::gateway::InterfaceFilter; use crate::net::host::HostApiKind; use crate::net::vhost::AlpnInfo; use crate::prelude::*; +use crate::util::FromStrParser; use crate::util::serde::{HandlerExtSerde, display_serializable}; +use crate::{GatewayId, HostId}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)] #[ts(export)] diff --git a/core/startos/src/net/host/mod.rs b/core/startos/src/net/host/mod.rs index 924a97615..8f9ab15e2 100644 --- a/core/startos/src/net/host/mod.rs +++ b/core/startos/src/net/host/mod.rs @@ -5,7 +5,6 @@ use std::panic::RefUnwindSafe; use clap::Parser; use imbl_value::InternedString; use itertools::Itertools; -use crate::{HostId, PackageId}; use rpc_toolkit::{Context, Empty, HandlerExt, OrEmpty, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -18,6 +17,7 @@ use crate::net::host::binding::{BindInfo, BindOptions, binding}; use crate::net::service_interface::HostnameInfo; use crate::net::tor::OnionAddress; use crate::prelude::*; +use crate::{HostId, PackageId}; pub mod address; pub mod binding; diff --git a/core/startos/src/net/net_controller.rs b/core/startos/src/net/net_controller.rs index 33061bb75..098726802 100644 --- a/core/startos/src/net/net_controller.rs +++ b/core/startos/src/net/net_controller.rs @@ -6,19 +6,17 @@ use color_eyre::eyre::eyre; use imbl::{OrdMap, vector}; use imbl_value::InternedString; use ipnet::IpNet; -use crate::{GatewayId, HostId, OptionExt, PackageId}; use tokio::sync::Mutex; use tokio::task::JoinHandle; use tokio_rustls::rustls::ClientConfig as TlsClientConfig; use tracing::instrument; -use crate::HOST_IP; use crate::db::model::Database; use crate::db::model::public::NetworkInterfaceType; use crate::error::ErrorCollection; use crate::hostname::Hostname; use crate::net::dns::DnsController; -use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE}; +use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE, add_iptables_rule}; use crate::net::gateway::{ AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter, PublicFilter, SecureFilter, TypeFilter, @@ -34,6 +32,7 @@ use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController}; use crate::prelude::*; use crate::service::effects::callbacks::ServiceCallbacks; use crate::util::serde::MaybeUtf8String; +use crate::{GatewayId, HOST_IP, HostId, OptionExt, PackageId}; pub struct NetController { pub(crate) db: TypedPatchDb, @@ -70,6 +69,22 @@ impl NetController { .de()? .0], )?); + add_iptables_rule( + false, + false, + &[ + "FORWARD", + "-i", + START9_BRIDGE_IFACE, + "-m", + "state", + "--state", + "NEW", + "-j", + "ACCEPT", + ], + ) + .await?; Ok(Self { db: db.clone(), tor, diff --git a/core/startos/src/net/service_interface.rs b/core/startos/src/net/service_interface.rs index 4c4cbf2c4..499e1a321 100644 --- a/core/startos/src/net/service_interface.rs +++ b/core/startos/src/net/service_interface.rs @@ -1,10 +1,11 @@ use std::net::{Ipv4Addr, Ipv6Addr}; use imbl_value::InternedString; -use crate::{GatewayId, HostId, ServiceInterfaceId}; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::{GatewayId, HostId, ServiceInterfaceId}; + #[derive(Clone, Debug, Deserialize, Serialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/net/socks.rs b/core/startos/src/net/socks.rs index fd4085fb3..0cd2645d7 100644 --- a/core/startos/src/net/socks.rs +++ b/core/startos/src/net/socks.rs @@ -2,7 +2,6 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::sync::Arc; use std::time::Duration; -use crate::util::future::NonDetachingJoinHandle; use socks5_impl::protocol::{Address, Reply}; use socks5_impl::server::auth::NoAuth; use socks5_impl::server::{AuthAdaptor, ClientConnection, Server}; @@ -12,6 +11,7 @@ use crate::HOST_IP; use crate::net::tor::TorController; use crate::prelude::*; use crate::util::actor::background::BackgroundJobQueue; +use crate::util::future::NonDetachingJoinHandle; pub const DEFAULT_SOCKS_LISTEN: SocketAddr = SocketAddr::V4(SocketAddrV4::new( Ipv4Addr::new(HOST_IP[0], HOST_IP[1], HOST_IP[2], HOST_IP[3]), diff --git a/core/startos/src/net/ssl.rs b/core/startos/src/net/ssl.rs index c2bca2411..2e5110a18 100644 --- a/core/startos/src/net/ssl.rs +++ b/core/startos/src/net/ssl.rs @@ -13,7 +13,6 @@ use openssl::bn::{BigNum, MsbOption}; use openssl::ec::{EcGroup, EcKey}; use openssl::error::ErrorStack; use openssl::hash::MessageDigest; -use openssl::nid::Nid; use openssl::pkey::{PKey, Private}; use openssl::x509::extension::{ AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName, @@ -42,12 +41,6 @@ use crate::net::web_server::{Accept, ExtractVisitor, TcpMetadata, extract}; use crate::prelude::*; use crate::util::serde::Pem; -pub fn gen_nistp256() -> Result, ErrorStack> { - PKey::from_ec_key(EcKey::generate(&*EcGroup::from_curve_name( - Nid::X9_62_PRIME256V1, - )?)?) -} - pub fn should_use_cert(cert: &X509Ref) -> Result { Ok(cert .not_before() @@ -71,7 +64,7 @@ pub struct CertStore { } impl CertStore { pub fn new(account: &AccountInfo) -> Result { - let int_key = generate_key()?; + let int_key = gen_nistp256()?; let int_cert = make_int_cert((&account.root_ca_key, &account.root_ca_cert), &int_key)?; Ok(Self { root_key: Pem::new(account.root_ca_key.clone()), @@ -283,10 +276,8 @@ fn rand_serial() -> Result { Ok(asn1) } #[instrument(skip_all)] -pub fn generate_key() -> Result, Error> { - let new_key = EcKey::generate(EC_GROUP.as_ref())?; - let key = PKey::from_ec_key(new_key)?; - Ok(key) +pub fn gen_nistp256() -> Result, Error> { + Ok(PKey::from_ec_key(EcKey::generate(EC_GROUP.as_ref())?)?) } #[instrument(skip_all)] @@ -324,6 +315,11 @@ pub fn make_root_cert( let ctx = builder.x509v3_context(None, Some(&cfg)); // subjectKeyIdentifier = hash let subject_key_identifier = SubjectKeyIdentifier::new().build(&ctx)?; + // authorityKeyIdentifier = keyid,issuer:always + let authority_key_identifier = AuthorityKeyIdentifier::new() + .keyid(false) + .issuer(true) + .build(&ctx)?; // basicConstraints = critical, CA:true, pathlen:0 let basic_constraints = BasicConstraints::new().critical().ca().build()?; // keyUsage = critical, digitalSignature, cRLSign, keyCertSign @@ -334,6 +330,7 @@ pub fn make_root_cert( .key_cert_sign() .build()?; builder.append_extension(subject_key_identifier)?; + builder.append_extension(authority_key_identifier)?; builder.append_extension(basic_constraints)?; builder.append_extension(key_usage)?; builder.sign(&root_key, MessageDigest::sha256())?; @@ -370,9 +367,9 @@ pub fn make_int_cert( // subjectKeyIdentifier = hash let subject_key_identifier = SubjectKeyIdentifier::new().build(&ctx)?; - // authorityKeyIdentifier = keyid:always,issuer + // authorityKeyIdentifier = keyid:always,issuer:always let authority_key_identifier = AuthorityKeyIdentifier::new() - .keyid(false) + .keyid(true) .issuer(true) .build(&ctx)?; // basicConstraints = critical, CA:true, pathlen:0 @@ -478,7 +475,7 @@ pub fn make_leaf_cert( // Google Apple and Mozilla reject certificate horizons longer than 398 days // https://techbeacon.com/security/google-apple-mozilla-enforce-1-year-max-security-certifications - let expiration = Asn1Time::days_from_now(365)?; + let expiration = Asn1Time::days_from_now(397)?; builder.set_not_after(&expiration)?; builder.set_serial_number(&*rand_serial()?)?; @@ -508,8 +505,8 @@ pub fn make_leaf_cert( let subject_key_identifier = SubjectKeyIdentifier::new().build(&ctx)?; let authority_key_identifier = AuthorityKeyIdentifier::new() - .keyid(false) - .issuer(true) + .keyid(true) + .issuer(false) .build(&ctx)?; let subject_alt_name = applicant.1.x509_extension().build(&ctx)?; let basic_constraints = BasicConstraints::new().build()?; diff --git a/core/startos/src/net/static_server.rs b/core/startos/src/net/static_server.rs index b7e50c7f8..b01080b25 100644 --- a/core/startos/src/net/static_server.rs +++ b/core/startos/src/net/static_server.rs @@ -22,7 +22,6 @@ use http::request::Parts as RequestParts; use http::{HeaderValue, Method, StatusCode}; use imbl_value::InternedString; use include_dir::Dir; -use crate::PackageId; use new_mime_guess::MimeGuess; use openssl::hash::MessageDigest; use openssl::x509::X509; @@ -33,8 +32,8 @@ use url::Url; use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext}; use crate::hostname::Hostname; -use crate::main_api; -use crate::middleware::auth::{Auth, HasValidSession}; +use crate::middleware::auth::Auth; +use crate::middleware::auth::session::ValidSessionToken; use crate::middleware::cors::Cors; use crate::middleware::db::SyncDb; use crate::net::gateway::GatewayInfo; @@ -49,6 +48,7 @@ use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::util::io::open_file; use crate::util::net::SyncBody; use crate::util::serde::BASE64; +use crate::{PackageId, main_api}; const NOT_FOUND: &[u8] = b"Not Found"; const METHOD_NOT_ALLOWED: &[u8] = b"Method Not Allowed"; @@ -80,7 +80,12 @@ impl UiContext for RpcContext { fn middleware(server: Server) -> HttpServer { server .middleware(Cors::new()) - .middleware(Auth::new()) + .middleware( + Auth::new() + .with_local_auth() + .with_signature_auth() + .with_session_auth(), + ) .middleware(SyncDb::new()) } fn extend_router(self, router: Router) -> Router { @@ -405,8 +410,9 @@ async fn if_authorized< f: F, ) -> Result { if let Err(e) = - HasValidSession::from_header(request.headers().get(http::header::COOKIE), ctx).await + ValidSessionToken::from_header(request.headers().get(http::header::COOKIE), ctx).await { + // TODO: other auth methods Ok(unauthorized(e, request.uri().path())) } else { f(request).await diff --git a/core/startos/src/net/tor/arti.rs b/core/startos/src/net/tor/arti.rs index 2202d737d..71a82b41d 100644 --- a/core/startos/src/net/tor/arti.rs +++ b/core/startos/src/net/tor/arti.rs @@ -11,7 +11,6 @@ use base64::Engine; use clap::Parser; use color_eyre::eyre::eyre; use futures::{FutureExt, StreamExt}; -use crate::util::future::NonDetachingJoinHandle; use imbl_value::InternedString; use itertools::Itertools; use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; @@ -31,7 +30,7 @@ use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::prelude::*; use crate::util::actor::background::BackgroundJobQueue; -use crate::util::future::Until; +use crate::util::future::{NonDetachingJoinHandle, Until}; use crate::util::io::ReadWriter; use crate::util::serde::{ BASE64, Base64, HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, diff --git a/core/startos/src/net/tor/ctor.rs b/core/startos/src/net/tor/ctor.rs index fb0d33f87..5ce62e137 100644 --- a/core/startos/src/net/tor/ctor.rs +++ b/core/startos/src/net/tor/ctor.rs @@ -10,7 +10,6 @@ use clap::Parser; use color_eyre::eyre::eyre; use futures::future::BoxFuture; use futures::{FutureExt, TryFutureExt, TryStreamExt}; -use crate::util::future::NonDetachingJoinHandle; use imbl::OrdMap; use imbl_value::InternedString; use lazy_static::lazy_static; @@ -31,6 +30,7 @@ use crate::logs::{LogSource, LogsParams, journalctl}; use crate::prelude::*; use crate::util::Invoke; use crate::util::collections::ordmap_retain; +use crate::util::future::NonDetachingJoinHandle; use crate::util::io::{ReadWriter, write_file_atomic}; use crate::util::serde::{ BASE64, Base64, HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, diff --git a/core/startos/src/net/tunnel.rs b/core/startos/src/net/tunnel.rs index d0cb5de6e..64e1ee284 100644 --- a/core/startos/src/net/tunnel.rs +++ b/core/startos/src/net/tunnel.rs @@ -1,12 +1,12 @@ use clap::Parser; use imbl_value::InternedString; -use crate::GatewayId; use patch_db::json_ptr::JsonPointer; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; +use crate::GatewayId; use crate::context::{CliContext, RpcContext}; use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType}; use crate::net::host::all_hosts; diff --git a/core/startos/src/net/utils.rs b/core/startos/src/net/utils.rs index b7ce2d23b..9f3a3682c 100644 --- a/core/startos/src/net/utils.rs +++ b/core/startos/src/net/utils.rs @@ -8,11 +8,11 @@ use futures::stream::BoxStream; use futures::{StreamExt, TryStreamExt}; use imbl_value::InternedString; use ipnet::{IpNet, Ipv4Net, Ipv6Net}; -use crate::GatewayId; use nix::net::if_::if_nametoindex; use tokio::net::{TcpListener, TcpStream}; use tokio::process::Command; +use crate::GatewayId; use crate::db::model::public::{IpInfo, NetworkInterfaceType}; use crate::prelude::*; use crate::util::Invoke; diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index 3f53f1011..3f4cbd181 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -9,9 +9,7 @@ use async_acme::acme::ACME_TLS_ALPN_NAME; use color_eyre::eyre::eyre; use futures::FutureExt; use futures::future::BoxFuture; -use crate::util::future::NonDetachingJoinHandle; use imbl_value::{InOMap, InternedString}; -use crate::ResultExt; use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn}; use serde::{Deserialize, Serialize}; use tokio::net::TcpStream; @@ -24,6 +22,7 @@ use tracing::instrument; use ts_rs::TS; use visit_rs::Visit; +use crate::ResultExt; use crate::context::{CliContext, RpcContext}; use crate::db::model::Database; use crate::db::model::public::AcmeSettings; @@ -42,7 +41,7 @@ use crate::net::tls::{ use crate::net::web_server::{Accept, AcceptStream, ExtractVisitor, TcpMetadata, extract}; use crate::prelude::*; use crate::util::collections::EqSet; -use crate::util::future::WeakFuture; +use crate::util::future::{NonDetachingJoinHandle, WeakFuture}; use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable}; use crate::util::sync::{SyncMutex, Watch}; diff --git a/core/startos/src/net/web_server.rs b/core/startos/src/net/web_server.rs index 47ce920b5..25e1df79a 100644 --- a/core/startos/src/net/web_server.rs +++ b/core/startos/src/net/web_server.rs @@ -12,7 +12,6 @@ use std::time::Duration; use axum::Router; use futures::future::Either; use futures::{FutureExt, TryFutureExt}; -use crate::util::future::NonDetachingJoinHandle; use http::Extensions; use hyper_util::rt::{TokioIo, TokioTimer}; use tokio::net::TcpListener; @@ -22,6 +21,7 @@ use visit_rs::{Visit, VisitFields, Visitor}; use crate::net::static_server::{UiContext, ui_router}; use crate::prelude::*; use crate::util::actor::background::BackgroundJobQueue; +use crate::util::future::NonDetachingJoinHandle; use crate::util::io::ReadWriter; use crate::util::sync::{SyncRwLock, Watch}; @@ -483,7 +483,7 @@ where .http2() .timer(TokioTimer::new()) .enable_connect_protocol() - .keep_alive_interval(Duration::from_secs(60)) + .keep_alive_interval(Duration::from_secs(25)) .keep_alive_timeout(Duration::from_secs(300)); let (queue, mut runner) = BackgroundJobQueue::new(); queue_cell.replace(Some(queue.clone())); diff --git a/core/startos/src/notifications.rs b/core/startos/src/notifications.rs index 4a5a9db38..4aac251f5 100644 --- a/core/startos/src/notifications.rs +++ b/core/startos/src/notifications.rs @@ -7,17 +7,17 @@ use clap::Parser; use clap::builder::ValueParserFactory; use color_eyre::eyre::eyre; use imbl_value::InternedString; -use crate::util::FromStrParser; -use crate::PackageId; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; +use crate::PackageId; use crate::backup::BackupReport; use crate::context::{CliContext, RpcContext}; use crate::db::model::DatabaseModel; use crate::prelude::*; +use crate::util::FromStrParser; use crate::util::serde::{HandlerExtSerde, const_true}; // #[command(subcommands(list, delete, delete_before, create))] diff --git a/core/startos/src/os_install/mod.rs b/core/startos/src/os_install/mod.rs index 0449b55d8..6ff9292b6 100644 --- a/core/startos/src/os_install/mod.rs +++ b/core/startos/src/os_install/mod.rs @@ -2,13 +2,11 @@ use std::path::{Path, PathBuf}; use clap::Parser; use color_eyre::eyre::eyre; -use crate::Error; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; -use crate::ARCH; use crate::context::config::ServerConfig; use crate::context::{CliContext, InstallContext}; use crate::disk::OsPartitionInfo; @@ -25,6 +23,7 @@ use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; use crate::util::Invoke; use crate::util::io::{TmpDir, delete_file, open_file}; use crate::util::serde::IoFormat; +use crate::{ARCH, Error}; mod gpt; mod mbr; diff --git a/core/startos/src/prelude.rs b/core/startos/src/prelude.rs index d42c2c9d9..369890500 100644 --- a/core/startos/src/prelude.rs +++ b/core/startos/src/prelude.rs @@ -1,4 +1,5 @@ pub use color_eyre::eyre::eyre; +pub use imbl_value::InternedString; pub use lazy_format::lazy_format; pub use tracing::instrument; diff --git a/core/startos/src/registry/asset.rs b/core/startos/src/registry/asset.rs index e8856c9ae..aa26ded04 100644 --- a/core/startos/src/registry/asset.rs +++ b/core/startos/src/registry/asset.rs @@ -3,7 +3,6 @@ use std::path::Path; use std::sync::Arc; use chrono::{DateTime, Utc}; -use crate::util::future::NonDetachingJoinHandle; use reqwest::Client; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWrite; @@ -20,6 +19,7 @@ use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::sign::commitment::{Commitment, Digestable}; use crate::sign::{AnySignature, AnyVerifyingKey}; use crate::upload::UploadingFile; +use crate::util::future::NonDetachingJoinHandle; #[derive(Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/registry/context.rs b/core/startos/src/registry/context.rs index 772616909..0375a46d3 100644 --- a/core/startos/src/registry/context.rs +++ b/core/startos/src/registry/context.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use chrono::Utc; use clap::Parser; +use cookie::{Cookie, Expiration, SameSite}; use http::HeaderMap; use imbl_value::InternedString; use patch_db::PatchDb; @@ -21,7 +22,9 @@ use url::Url; use crate::context::config::{CONFIG_PATH, ContextConfig}; use crate::context::{CliContext, RpcContext}; -use crate::middleware::signature::SignatureAuthContext; +use crate::middleware::auth::DbContext; +use crate::middleware::auth::local::LocalAuthContext; +use crate::middleware::auth::signature::SignatureAuthContext; use crate::prelude::*; use crate::registry::RegistryDatabase; use crate::registry::device_info::{DEVICE_INFO_HEADER, DeviceInfo}; @@ -29,7 +32,7 @@ use crate::registry::migrations::run_migrations; use crate::registry::signer::SignerInfo; use crate::rpc_continuations::RpcContinuations; use crate::sign::AnyVerifyingKey; -use crate::util::io::append_file; +use crate::util::io::{append_file, read_file_to_string}; const DEFAULT_REGISTRY_LISTEN: SocketAddr = SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), 5959); @@ -104,6 +107,8 @@ impl RegistryContext { } db.mutate(|db| run_migrations(db)).await.result?; + Self::init_auth_cookie().await?; + let tor_proxy_url = config .tor_proxy .clone() @@ -169,9 +174,11 @@ impl CallRemote for CliContext { params: Value, _: Empty, ) -> Result { + let cookie = read_file_to_string(RegistryContext::LOCAL_AUTH_COOKIE_PATH).await; + let url = if let Some(url) = self.registry_url.clone() { url - } else if !self.registry_hostname.is_empty() { + } else if cookie.is_ok() || !self.registry_hostname.is_empty() { let mut url: Url = format!( "http://{}", self.registry_listen.unwrap_or(DEFAULT_REGISTRY_LISTEN) @@ -179,7 +186,8 @@ impl CallRemote for CliContext { .parse() .map_err(Error::from)?; url.path_segments_mut() - .map_err(|_| Error::new(eyre!("cannot extend URL path"), ErrorKind::ParseUrl))? + .map_err(|_| eyre!("Url cannot be base")) + .with_kind(crate::ErrorKind::ParseUrl)? .push("rpc") .push("v0"); url @@ -189,6 +197,26 @@ impl CallRemote for CliContext { ); }; + if let Ok(local) = cookie { + let cookie_url = match url.host() { + Some(url::Host::Ipv4(ip)) if ip.is_loopback() => url.clone(), + Some(url::Host::Ipv6(ip)) if ip.is_loopback() => url.clone(), + _ => format!("http://{DEFAULT_REGISTRY_LISTEN}").parse()?, + }; + self.cookie_store + .lock() + .unwrap() + .insert_raw( + &Cookie::build(("local", local)) + .domain(cookie_url.host_str().unwrap_or("localhost")) + .expires(Expiration::Session) + .same_site(SameSite::Strict) + .build(), + &cookie_url, + ) + .with_kind(crate::ErrorKind::Network)?; + } + method = method.strip_prefix("registry.").unwrap_or(method); let sig_context = self .registry_hostname @@ -196,7 +224,7 @@ impl CallRemote for CliContext { .cloned() .or_else(|| url.host().as_ref().map(InternedString::from_display)); - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, url, HeaderMap::new(), @@ -230,7 +258,7 @@ impl CallRemote for RpcContext { method = method.strip_prefix("registry.").unwrap_or(method); let sig_context = registry.host_str().map(InternedString::from); - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, registry, headers, @@ -257,13 +285,19 @@ pub struct AdminLogRecord { pub key: AnyVerifyingKey, } -impl SignatureAuthContext for RegistryContext { +impl DbContext for RegistryContext { type Database = RegistryDatabase; - type AdditionalMetadata = RegistryAuthMetadata; - type CheckPubkeyRes = Option<(AnyVerifyingKey, SignerInfo)>; fn db(&self) -> &TypedPatchDb { &self.db } +} +impl LocalAuthContext for RegistryContext { + const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/registry.authcookie"; + const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:root"; +} +impl SignatureAuthContext for RegistryContext { + type AdditionalMetadata = RegistryAuthMetadata; + type CheckPubkeyRes = Option<(AnyVerifyingKey, SignerInfo)>; async fn sig_context( &self, ) -> impl IntoIterator + Send, Error>> + Send { @@ -274,17 +308,14 @@ impl SignatureAuthContext for RegistryContext { pubkey: Option<&AnyVerifyingKey>, metadata: Self::AdditionalMetadata, ) -> Result { - if metadata.admin { - if let Some(pubkey) = pubkey { - let (guid, admin) = db.as_index().as_signers().get_signer_info(pubkey)?; - if db.as_admins().de()?.contains(&guid) { - return Ok(Some((pubkey.clone(), admin))); - } + if let Some(pubkey) = pubkey { + let (guid, admin) = db.as_index().as_signers().get_signer_info(pubkey)?; + if !metadata.admin || db.as_admins().de()?.contains(&guid) { + return Ok(Some((pubkey.clone(), admin))); } - Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) - } else { - Ok(None) } + + Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) } async fn post_auth_hook( &self, diff --git a/core/startos/src/registry/info.rs b/core/startos/src/registry/info.rs index 4afedbfb5..5d18885e4 100644 --- a/core/startos/src/registry/info.rs +++ b/core/startos/src/registry/info.rs @@ -4,7 +4,6 @@ use std::path::PathBuf; use clap::Parser; use imbl_value::InternedString; use itertools::Itertools; -use crate::util::DataUrl; use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -13,12 +12,14 @@ use crate::context::CliContext; use crate::prelude::*; use crate::registry::context::RegistryContext; use crate::registry::package::index::Category; +use crate::util::DataUrl; use crate::util::serde::{HandlerExtSerde, WithIoFormat}; pub fn info_api() -> ParentHandler> { ParentHandler::>::new() .root_handler( from_fn_async(get_info) + .with_metadata("authenticated", Value::Bool(false)) .with_display_serializable() .with_about("Display registry name, icon, and package categories") .with_call_remote::(), diff --git a/core/startos/src/registry/mod.rs b/core/startos/src/registry/mod.rs index 8bea37624..bed299586 100644 --- a/core/startos/src/registry/mod.rs +++ b/core/startos/src/registry/mod.rs @@ -3,14 +3,13 @@ use std::collections::{BTreeMap, BTreeSet}; use axum::Router; use futures::future::ready; use imbl_value::InternedString; -use crate::util::DataUrl; use rpc_toolkit::{Context, HandlerExt, ParentHandler, Server, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::context::CliContext; +use crate::middleware::auth::Auth; use crate::middleware::cors::Cors; -use crate::middleware::signature::SignatureAuth; use crate::net::static_server::{bad_request, not_found, server_error}; use crate::prelude::*; use crate::registry::context::RegistryContext; @@ -19,6 +18,7 @@ use crate::registry::os::index::OsIndex; use crate::registry::package::index::PackageIndex; use crate::registry::signer::SignerInfo; use crate::rpc_continuations::Guid; +use crate::util::DataUrl; use crate::util::serde::HandlerExtSerde; pub mod admin; @@ -108,7 +108,7 @@ pub fn registry_router(ctx: RegistryContext) -> Router { any( Server::new(move || ready(Ok(ctx.clone())), registry_api()) .middleware(Cors::new()) - .middleware(SignatureAuth::new()) + .middleware(Auth::new().with_local_auth().with_signature_auth()) .middleware(DeviceInfoMiddleware::new()), ) }) diff --git a/core/startos/src/registry/os/asset/get.rs b/core/startos/src/registry/os/asset/get.rs index 604b4dad4..5904e9b0d 100644 --- a/core/startos/src/registry/os/asset/get.rs +++ b/core/startos/src/registry/os/asset/get.rs @@ -24,21 +24,36 @@ use crate::util::io::{AtomicFile, open_file}; pub fn get_api() -> ParentHandler { ParentHandler::new() - .subcommand("iso", from_fn_async(get_iso).no_cli()) + .subcommand( + "iso", + from_fn_async(get_iso) + .with_metadata("authenticated", Value::Bool(false)) + .no_cli(), + ) .subcommand( "iso", from_fn_async(cli_get_os_asset) .no_display() .with_about("Download iso"), ) - .subcommand("img", from_fn_async(get_img).no_cli()) + .subcommand( + "img", + from_fn_async(get_img) + .with_metadata("authenticated", Value::Bool(false)) + .no_cli(), + ) .subcommand( "img", from_fn_async(cli_get_os_asset) .no_display() .with_about("Download img"), ) - .subcommand("squashfs", from_fn_async(get_squashfs).no_cli()) + .subcommand( + "squashfs", + from_fn_async(get_squashfs) + .with_metadata("authenticated", Value::Bool(false)) + .no_cli(), + ) .subcommand( "squashfs", from_fn_async(cli_get_os_asset) diff --git a/core/startos/src/registry/os/mod.rs b/core/startos/src/registry/os/mod.rs index 3c2b95864..d4d308281 100644 --- a/core/startos/src/registry/os/mod.rs +++ b/core/startos/src/registry/os/mod.rs @@ -1,6 +1,7 @@ use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use crate::context::CliContext; +use crate::prelude::*; use crate::util::serde::HandlerExtSerde; pub const SIG_CONTEXT: &str = "startos"; @@ -14,6 +15,7 @@ pub fn os_api() -> ParentHandler { .subcommand( "index", from_fn_async(index::get_os_index) + .with_metadata("authenticated", Value::Bool(false)) .with_display_serializable() .with_about("List index of OS versions") .with_call_remote::(), diff --git a/core/startos/src/registry/os/version/mod.rs b/core/startos/src/registry/os/version/mod.rs index 29105d577..d6b68652f 100644 --- a/core/startos/src/registry/os/version/mod.rs +++ b/core/startos/src/registry/os/version/mod.rs @@ -45,6 +45,7 @@ pub fn version_api() -> ParentHandler { .subcommand( "get", from_fn_async(get_version) + .with_metadata("authenticated", Value::Bool(false)) .with_metadata("get_device_info", Value::Bool(true)) .with_display_serializable() .with_custom_display_fn(|handle, result| { diff --git a/core/startos/src/registry/os/version/signer.rs b/core/startos/src/registry/os/version/signer.rs index eab8215a3..a668e1c17 100644 --- a/core/startos/src/registry/os/version/signer.rs +++ b/core/startos/src/registry/os/version/signer.rs @@ -35,6 +35,7 @@ pub fn signer_api() -> ParentHandler { .subcommand( "list", from_fn_async(list_version_signers) + .with_metadata("authenticated", Value::Bool(false)) .with_display_serializable() .with_custom_display_fn(|handle, result| display_signers(handle.params, result)) .with_about("List version signers and related signer info") diff --git a/core/startos/src/registry/package/add.rs b/core/startos/src/registry/package/add.rs index 271a6c7cc..421b5d73f 100644 --- a/core/startos/src/registry/package/add.rs +++ b/core/startos/src/registry/package/add.rs @@ -4,13 +4,12 @@ use std::sync::Arc; use clap::Parser; use imbl_value::InternedString; use itertools::Itertools; -use crate::util::VersionString; -use crate::PackageId; use rpc_toolkit::HandlerArgs; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; +use crate::PackageId; use crate::context::CliContext; use crate::prelude::*; use crate::progress::{FullProgressTracker, ProgressTrackerWriter, ProgressUnits}; @@ -23,6 +22,7 @@ use crate::s9pk::v2::SIG_CONTEXT; use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::sign::ed25519::Ed25519; use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; +use crate::util::VersionString; use crate::util::io::TrackingIO; #[derive(Debug, Deserialize, Serialize, TS)] diff --git a/core/startos/src/registry/package/category.rs b/core/startos/src/registry/package/category.rs index e4899bef0..131ab42f2 100644 --- a/core/startos/src/registry/package/category.rs +++ b/core/startos/src/registry/package/category.rs @@ -2,11 +2,11 @@ use std::collections::BTreeMap; use clap::Parser; use imbl_value::InternedString; -use crate::PackageId; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::PackageId; use crate::context::CliContext; use crate::prelude::*; use crate::registry::context::RegistryContext; @@ -50,6 +50,7 @@ pub fn category_api() -> ParentHandler { .subcommand( "list", from_fn_async(list_categories) + .with_metadata("authenticated", Value::Bool(false)) .with_display_serializable() .with_custom_display_fn(|params, categories| { display_categories(params.params, categories) diff --git a/core/startos/src/registry/package/get.rs b/core/startos/src/registry/package/get.rs index b395343d2..775795429 100644 --- a/core/startos/src/registry/package/get.rs +++ b/core/startos/src/registry/package/get.rs @@ -5,10 +5,10 @@ use clap::{Parser, ValueEnum}; use exver::{ExtendedVersion, VersionRange}; use imbl_value::{InternedString, json}; use itertools::Itertools; -use crate::PackageId; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::PackageId; use crate::context::CliContext; use crate::prelude::*; use crate::progress::{FullProgressTracker, ProgressUnits}; diff --git a/core/startos/src/registry/package/index.rs b/core/startos/src/registry/package/index.rs index 1877da597..5213ac184 100644 --- a/core/startos/src/registry/package/index.rs +++ b/core/startos/src/registry/package/index.rs @@ -3,12 +3,11 @@ use std::collections::{BTreeMap, BTreeSet}; use chrono::Utc; use exver::{Version, VersionRange}; use imbl_value::InternedString; -use crate::util::{DataUrl, VersionString}; -use crate::PackageId; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; +use crate::PackageId; use crate::prelude::*; use crate::registry::asset::RegistryAsset; use crate::registry::context::RegistryContext; @@ -20,6 +19,7 @@ use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements}; use crate::s9pk::merkle_archive::source::FileSource; use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::sign::{AnySignature, AnyVerifyingKey}; +use crate::util::{DataUrl, VersionString}; #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/registry/package/mod.rs b/core/startos/src/registry/package/mod.rs index ad6a0abed..d868c4364 100644 --- a/core/startos/src/registry/package/mod.rs +++ b/core/startos/src/registry/package/mod.rs @@ -15,6 +15,7 @@ pub fn package_api() -> ParentHandler { .subcommand( "index", from_fn_async(index::get_package_index) + .with_metadata("authenticated", Value::Bool(false)) .with_display_serializable() .with_about("List packages and categories") .with_call_remote::(), @@ -46,6 +47,7 @@ pub fn package_api() -> ParentHandler { .subcommand( "get", from_fn_async(get::get_package) + .with_metadata("authenticated", Value::Bool(false)) .with_metadata("get_device_info", Value::Bool(true)) .with_display_serializable() .with_custom_display_fn(|handle, result| { diff --git a/core/startos/src/registry/package/signer.rs b/core/startos/src/registry/package/signer.rs index 69d37b092..3ab73d521 100644 --- a/core/startos/src/registry/package/signer.rs +++ b/core/startos/src/registry/package/signer.rs @@ -2,11 +2,11 @@ use std::collections::BTreeMap; use clap::Parser; use exver::VersionRange; -use crate::PackageId; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::PackageId; use crate::context::CliContext; use crate::prelude::*; use crate::registry::admin::display_package_signers; @@ -36,6 +36,7 @@ pub fn signer_api() -> ParentHandler { .subcommand( "list", from_fn_async(list_package_signers) + .with_metadata("authenticated", Value::Bool(false)) .with_display_serializable() .with_custom_display_fn(|handle, result| { display_package_signers(handle.params, result) diff --git a/core/startos/src/registry/signer.rs b/core/startos/src/registry/signer.rs index 8f2dddd64..3a4fc2d0d 100644 --- a/core/startos/src/registry/signer.rs +++ b/core/startos/src/registry/signer.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; use itertools::Itertools; -use crate::util::FromStrParser; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; @@ -11,6 +10,7 @@ use url::Url; use crate::prelude::*; use crate::sign::commitment::Digestable; use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; +use crate::util::FromStrParser; #[derive(Debug, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/rpc_continuations.rs b/core/startos/src/rpc_continuations.rs index c894391b7..aa3f24236 100644 --- a/core/startos/src/rpc_continuations.rs +++ b/core/startos/src/rpc_continuations.rs @@ -12,14 +12,13 @@ use clap::builder::ValueParserFactory; use futures::future::BoxFuture; use futures::{Future, FutureExt}; use imbl_value::InternedString; -use crate::util::FromStrParser; use tokio::sync::{Mutex as AsyncMutex, broadcast}; use ts_rs::TS; #[allow(unused_imports)] use crate::prelude::*; use crate::util::future::TimedResource; -use crate::util::new_guid; +use crate::util::{FromStrParser, new_guid}; #[derive( Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, TS, diff --git a/core/startos/src/s9pk/rpc.rs b/core/startos/src/s9pk/rpc.rs index a6af628e7..ec7dd3696 100644 --- a/core/startos/src/s9pk/rpc.rs +++ b/core/startos/src/s9pk/rpc.rs @@ -2,11 +2,11 @@ use std::path::PathBuf; use std::sync::Arc; use clap::Parser; -use crate::ImageId; use rpc_toolkit::{Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::ImageId; use crate::context::CliContext; use crate::prelude::*; use crate::s9pk::manifest::Manifest; diff --git a/core/startos/src/s9pk/v1/manifest.rs b/core/startos/src/s9pk/v1/manifest.rs index 0ce5e67a6..993231138 100644 --- a/core/startos/src/s9pk/v1/manifest.rs +++ b/core/startos/src/s9pk/v1/manifest.rs @@ -4,15 +4,15 @@ use std::path::{Path, PathBuf}; use exver::{Version, VersionRange}; use imbl_value::InternedString; use indexmap::IndexMap; -pub use crate::PackageId; -use crate::{ActionId, HealthCheckId, ImageId, VolumeId}; use serde::{Deserialize, Serialize}; use url::Url; +pub use crate::PackageId; use crate::prelude::*; use crate::s9pk::git_hash::GitHash; use crate::s9pk::manifest::{Alerts, Description}; use crate::util::serde::{Duration, IoFormat, Regex}; +use crate::{ActionId, HealthCheckId, ImageId, VolumeId}; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] diff --git a/core/startos/src/s9pk/v1/reader.rs b/core/startos/src/s9pk/v1/reader.rs index 7becf0475..8ebe55da6 100644 --- a/core/startos/src/s9pk/v1/reader.rs +++ b/core/startos/src/s9pk/v1/reader.rs @@ -9,7 +9,6 @@ use std::task::{Context, Poll}; use color_eyre::eyre::eyre; use digest::Output; use ed25519_dalek::VerifyingKey; -use crate::{ImageId, PackageId}; use sha2::{Digest, Sha512}; use tokio::fs::File; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, BufReader, ReadBuf}; @@ -21,6 +20,7 @@ use crate::prelude::*; use crate::s9pk::v1::docker::DockerReader; use crate::util::VersionString; use crate::util::io::open_file; +use crate::{ImageId, PackageId}; #[pin_project::pin_project] #[derive(Debug)] diff --git a/core/startos/src/s9pk/v2/manifest.rs b/core/startos/src/s9pk/v2/manifest.rs index 0e5d3badc..23e518422 100644 --- a/core/startos/src/s9pk/v2/manifest.rs +++ b/core/startos/src/s9pk/v2/manifest.rs @@ -4,22 +4,21 @@ use std::path::Path; use color_eyre::eyre::eyre; use exver::{Version, VersionRange}; use imbl_value::InternedString; -pub use crate::PackageId; -use crate::util::mime; -use crate::{ImageId, VolumeId}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; +pub use crate::PackageId; use crate::dependencies::Dependencies; use crate::prelude::*; use crate::s9pk::git_hash::GitHash; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; use crate::s9pk::merkle_archive::expected::{Expected, Filter}; use crate::s9pk::v2::pack::ImageConfig; -use crate::util::VersionString; use crate::util::serde::Regex; +use crate::util::{VersionString, mime}; use crate::version::{Current, VersionT}; +use crate::{ImageId, VolumeId}; fn current_version() -> Version { Current::default().semver() diff --git a/core/startos/src/s9pk/v2/mod.rs b/core/startos/src/s9pk/v2/mod.rs index 3453cc21d..fcaa70531 100644 --- a/core/startos/src/s9pk/v2/mod.rs +++ b/core/startos/src/s9pk/v2/mod.rs @@ -3,10 +3,9 @@ use std::path::Path; use std::sync::Arc; use imbl_value::InternedString; -use crate::util::{DataUrl, mime}; -use crate::PackageId; use tokio::fs::File; +use crate::PackageId; use crate::dependencies::DependencyMetadata; use crate::prelude::*; use crate::s9pk::manifest::Manifest; @@ -20,6 +19,7 @@ use crate::s9pk::v2::pack::{ImageSource, PackSource}; use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::util::io::{TmpDir, open_file}; use crate::util::serde::IoFormat; +use crate::util::{DataUrl, mime}; const MAGIC_AND_VERSION: &[u8] = &[0x3b, 0x3b, 0x02]; diff --git a/core/startos/src/service/action.rs b/core/startos/src/service/action.rs index 713229ec5..0e75e1c3c 100644 --- a/core/startos/src/service/action.rs +++ b/core/startos/src/service/action.rs @@ -2,8 +2,6 @@ use std::collections::BTreeMap; use std::time::Duration; use imbl_value::json; -use crate::service::ProcedureName; -use crate::{ActionId, PackageId, ReplayId}; use crate::action::{ActionInput, ActionResult}; use crate::db::model::package::{ @@ -11,10 +9,11 @@ use crate::db::model::package::{ }; use crate::prelude::*; use crate::rpc_continuations::Guid; -use crate::service::{Service, ServiceActor}; +use crate::service::{ProcedureName, Service, ServiceActor}; use crate::util::actor::background::BackgroundJobQueue; use crate::util::actor::{ConflictBuilder, Handler}; use crate::util::serde::is_partial_of; +use crate::{ActionId, PackageId, ReplayId}; pub(super) struct GetActionInput { id: ActionId, diff --git a/core/startos/src/service/effects/action.rs b/core/startos/src/service/effects/action.rs index 3e195a92a..ff98dfd13 100644 --- a/core/startos/src/service/effects/action.rs +++ b/core/startos/src/service/effects/action.rs @@ -1,6 +1,5 @@ use std::collections::BTreeSet; -use crate::{ActionId, PackageId, ReplayId}; use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async}; use crate::action::{ActionInput, ActionResult, display_action_result}; @@ -11,6 +10,7 @@ use crate::rpc_continuations::Guid; use crate::service::cli::ContainerCliContext; use crate::service::effects::prelude::*; use crate::util::serde::HandlerExtSerde; +use crate::{ActionId, PackageId, ReplayId}; pub fn action_api() -> ParentHandler { ParentHandler::new() diff --git a/core/startos/src/service/effects/callbacks.rs b/core/startos/src/service/effects/callbacks.rs index 2a94b2627..59d61f39c 100644 --- a/core/startos/src/service/effects/callbacks.rs +++ b/core/startos/src/service/effects/callbacks.rs @@ -5,10 +5,8 @@ use std::time::{Duration, SystemTime}; use clap::Parser; use futures::future::join_all; -use crate::util::future::NonDetachingJoinHandle; use imbl::{Vector, vector}; use imbl_value::InternedString; -use crate::{HostId, PackageId, ServiceInterfaceId}; use serde::{Deserialize, Serialize}; use tracing::warn; use ts_rs::TS; @@ -20,6 +18,8 @@ use crate::service::effects::net::ssl::Algorithm; use crate::service::rpc::{CallbackHandle, CallbackId}; use crate::service::{Service, ServiceActorSeed}; use crate::util::collections::EqMap; +use crate::util::future::NonDetachingJoinHandle; +use crate::{HostId, PackageId, ServiceInterfaceId}; #[derive(Default)] pub struct ServiceCallbacks(Mutex); diff --git a/core/startos/src/service/effects/control.rs b/core/startos/src/service/effects/control.rs index 5dd0e794a..292a0bb9f 100644 --- a/core/startos/src/service/effects/control.rs +++ b/core/startos/src/service/effects/control.rs @@ -2,13 +2,13 @@ use std::str::FromStr; use chrono::Utc; use clap::builder::ValueParserFactory; -use crate::util::FromStrParser; -use crate::PackageId; +use crate::PackageId; use crate::service::RebuildParams; use crate::service::effects::prelude::*; use crate::service::rpc::CallbackId; use crate::status::{DesiredStatus, StatusInfo}; +use crate::util::FromStrParser; pub async fn rebuild(context: EffectContext) -> Result<(), Error> { let seed = context.deref()?.seed.clone(); diff --git a/core/startos/src/service/effects/dependency.rs b/core/startos/src/service/effects/dependency.rs index 876e8dc47..419e4f2be 100644 --- a/core/startos/src/service/effects/dependency.rs +++ b/core/startos/src/service/effects/dependency.rs @@ -5,10 +5,7 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; use exver::VersionRange; use imbl_value::InternedString; -use crate::util::{FromStrParser, VersionString}; -use crate::{HealthCheckId, PackageId, ReplayId, VolumeId}; -use crate::DATA_DIR; use crate::db::model::package::{ CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind, ManifestPreference, TaskEntry, @@ -19,7 +16,9 @@ use crate::disk::mount::filesystem::{FileSystem, MountType}; use crate::disk::mount::util::{is_mountpoint, unmount}; use crate::service::effects::prelude::*; use crate::status::health_check::NamedHealthCheckResult; +use crate::util::{FromStrParser, VersionString}; use crate::volume::data_dir; +use crate::{DATA_DIR, HealthCheckId, PackageId, ReplayId, VolumeId}; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] diff --git a/core/startos/src/service/effects/health.rs b/core/startos/src/service/effects/health.rs index 548c02dbd..aa1d8a3d3 100644 --- a/core/startos/src/service/effects/health.rs +++ b/core/startos/src/service/effects/health.rs @@ -1,5 +1,4 @@ use crate::HealthCheckId; - use crate::service::effects::prelude::*; use crate::status::health_check::NamedHealthCheckResult; diff --git a/core/startos/src/service/effects/net/bind.rs b/core/startos/src/service/effects/net/bind.rs index 252423d94..f25b22bd4 100644 --- a/core/startos/src/service/effects/net/bind.rs +++ b/core/startos/src/service/effects/net/bind.rs @@ -1,7 +1,6 @@ -use crate::{HostId, PackageId}; - use crate::net::host::binding::{BindId, BindOptions, NetInfo}; use crate::service::effects::prelude::*; +use crate::{HostId, PackageId}; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/service/effects/net/host.rs b/core/startos/src/service/effects/net/host.rs index e042b58bd..ebd1b80c8 100644 --- a/core/startos/src/service/effects/net/host.rs +++ b/core/startos/src/service/effects/net/host.rs @@ -1,9 +1,8 @@ -use crate::{HostId, PackageId}; - use crate::net::host::Host; use crate::service::effects::callbacks::CallbackHandler; use crate::service::effects::prelude::*; use crate::service::rpc::CallbackId; +use crate::{HostId, PackageId}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/service/effects/net/info.rs b/core/startos/src/service/effects/net/info.rs index 74ed7c8da..f14ee72dc 100644 --- a/core/startos/src/service/effects/net/info.rs +++ b/core/startos/src/service/effects/net/info.rs @@ -1,7 +1,6 @@ use std::net::Ipv4Addr; use crate::PackageId; - use crate::service::effects::callbacks::CallbackHandler; use crate::service::effects::prelude::*; use crate::service::rpc::CallbackId; diff --git a/core/startos/src/service/effects/net/interface.rs b/core/startos/src/service/effects/net/interface.rs index 94b416846..ff0452976 100644 --- a/core/startos/src/service/effects/net/interface.rs +++ b/core/startos/src/service/effects/net/interface.rs @@ -1,12 +1,12 @@ use std::collections::BTreeMap; use imbl::vector; -use crate::{PackageId, ServiceInterfaceId}; use crate::net::service_interface::{AddressInfo, ServiceInterface, ServiceInterfaceType}; use crate::service::effects::callbacks::CallbackHandler; use crate::service::effects::prelude::*; use crate::service::rpc::CallbackId; +use crate::{PackageId, ServiceInterfaceId}; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index fe34c820e..95e53f83c 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -13,10 +13,8 @@ use clap::Parser; use futures::future::BoxFuture; use futures::stream::FusedStream; use futures::{FutureExt, SinkExt, StreamExt, TryStreamExt}; -use crate::util::future::NonDetachingJoinHandle; use imbl_value::{InternedString, json}; use itertools::Itertools; -use crate::{ActionId, HostId, ImageId, PackageId}; use nix::sys::signal::Signal; use persistent_container::{PersistentContainer, Subcontainer}; use rpc_toolkit::HandlerArgs; @@ -47,12 +45,13 @@ use crate::service::service_map::InstallProgressHandles; use crate::service::uninstall::cleanup; use crate::util::Never; use crate::util::actor::concurrent::ConcurrentActor; +use crate::util::future::NonDetachingJoinHandle; use crate::util::io::{AsyncReadStream, AtomicFile, TermSize, delete_file}; use crate::util::net::WebSocketExt; use crate::util::serde::Pem; use crate::util::sync::SyncMutex; use crate::volume::data_dir; -use crate::{CAP_1_KiB, DATA_DIR}; +use crate::{ActionId, CAP_1_KiB, DATA_DIR, HostId, ImageId, PackageId}; pub mod action; pub mod cli; diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index 5a1194945..e2faf0a8c 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -5,11 +5,8 @@ use std::time::Duration; use futures::Future; use futures::future::ready; -use crate::util::future::NonDetachingJoinHandle; use imbl::{Vector, vector}; use imbl_value::InternedString; -use crate::service::ProcedureName; -use crate::{ImageId, VolumeId}; use rpc_toolkit::{Empty, Server, ShutdownHandle}; use serde::de::DeserializeOwned; use tokio::process::Command; @@ -34,12 +31,13 @@ use crate::service::effects::handler; use crate::service::rpc::{ CallbackHandle, CallbackId, CallbackParams, ExitParams, InitKind, InitParams, }; -use crate::service::{Service, rpc}; +use crate::service::{ProcedureName, Service, rpc}; use crate::util::Invoke; +use crate::util::future::NonDetachingJoinHandle; use crate::util::io::create_file; use crate::util::rpc_client::UnixRpcClient; use crate::volume::data_dir; -use crate::{ARCH, DATA_DIR, PACKAGE_DATA}; +use crate::{ARCH, DATA_DIR, ImageId, PACKAGE_DATA, VolumeId}; const RPC_CONNECT_TIMEOUT: Duration = Duration::from_secs(30); diff --git a/core/startos/src/service/rpc.rs b/core/startos/src/service/rpc.rs index 777213443..b5c8ed01c 100644 --- a/core/startos/src/service/rpc.rs +++ b/core/startos/src/service/rpc.rs @@ -7,16 +7,15 @@ use clap::builder::ValueParserFactory; use exver::{ExtendedVersion, VersionRange}; use imbl::Vector; use imbl_value::{InternedString, Value}; -use crate::service::ProcedureName; -use crate::util::FromStrParser; use rpc_toolkit::Empty; use rpc_toolkit::yajrc::RpcMethod; use ts_rs::TS; use crate::prelude::*; use crate::rpc_continuations::Guid; +use crate::service::ProcedureName; use crate::service::persistent_container::PersistentContainer; -use crate::util::Never; +use crate::util::{FromStrParser, Never}; #[derive(Clone, serde::Deserialize, serde::Serialize, TS)] #[serde(rename_all = "kebab-case")] diff --git a/core/startos/src/service/service_actor.rs b/core/startos/src/service/service_actor.rs index 755f3ea42..724be7f16 100644 --- a/core/startos/src/service/service_actor.rs +++ b/core/startos/src/service/service_actor.rs @@ -132,9 +132,7 @@ async fn service_actor_loop<'a>( .filter(|task| task.kind == TransitionKind::BackingUp); *transition = task.or_else(|| Some(seed.backup())); } - _ => { - *transition = None; - } + _ => (), }; Ok(()) } diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index 53ff58748..fe8192d26 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -8,9 +8,7 @@ use exver::VersionRange; use futures::future::{BoxFuture, Fuse}; use futures::stream::FuturesUnordered; use futures::{Future, FutureExt, StreamExt, TryFutureExt}; -use crate::util::future::NonDetachingJoinHandle; use imbl::OrdMap; -use crate::error::ErrorData; use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock, oneshot}; use tracing::instrument; use url::Url; @@ -21,6 +19,7 @@ use crate::db::model::package::{ InstallingInfo, InstallingState, PackageDataEntry, PackageState, UpdatingState, }; use crate::disk::mount::guard::GenericMountGuard; +use crate::error::ErrorData; use crate::install::PKG_ARCHIVE_DIR; use crate::notifications::{NotificationLevel, notify}; use crate::prelude::*; @@ -32,6 +31,7 @@ use crate::service::rpc::{ExitParams, InitKind}; use crate::service::{LoadDisposition, Service, ServiceRef}; use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; use crate::status::{DesiredStatus, StatusInfo}; +use crate::util::future::NonDetachingJoinHandle; use crate::util::serde::{Base32, Pem}; use crate::util::sync::SyncMutex; @@ -115,7 +115,18 @@ impl ServiceMap { shutdown_err = service.shutdown(None).await; } match Service::load(ctx, id, disposition).await { - Ok(s) => *service = s.into(), + Ok(s) => { + ctx.db + .mutate(|db| { + if let Some(pde) = db.as_public_mut().as_package_data_mut().as_idx_mut(id) { + pde.as_status_info_mut().as_error_mut().ser(&None)?; + } + Ok(()) + }) + .await + .result?; + *service = s.into(); + } Err(e) => { tracing::error!("Error loading service: {e}"); tracing::debug!("{e:?}"); diff --git a/core/startos/src/service/transition/backup.rs b/core/startos/src/service/transition/backup.rs index 1ed404c30..30787311e 100644 --- a/core/startos/src/service/transition/backup.rs +++ b/core/startos/src/service/transition/backup.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use futures::future::BoxFuture; use futures::{FutureExt, TryFutureExt}; -use crate::service::ProcedureName; use rpc_toolkit::yajrc::RpcError; use crate::disk::mount::filesystem::ReadWrite; @@ -11,7 +10,7 @@ use crate::rpc_continuations::Guid; use crate::service::action::GetActionInput; use crate::service::start_stop::StartStop; use crate::service::transition::{Transition, TransitionKind}; -use crate::service::{ServiceActor, ServiceActorSeed}; +use crate::service::{ProcedureName, ServiceActor, ServiceActorSeed}; use crate::status::DesiredStatus; use crate::util::actor::background::BackgroundJobQueue; use crate::util::actor::{ConflictBuilder, Handler}; diff --git a/core/startos/src/service/uninstall.rs b/core/startos/src/service/uninstall.rs index 4a93dff19..c245d0687 100644 --- a/core/startos/src/service/uninstall.rs +++ b/core/startos/src/service/uninstall.rs @@ -1,12 +1,10 @@ use std::path::Path; -use crate::PackageId; - use crate::context::RpcContext; use crate::db::model::package::{InstalledState, InstallingInfo, InstallingState, PackageState}; use crate::prelude::*; use crate::volume::PKG_VOLUME_DIR; -use crate::{DATA_DIR, PACKAGE_DATA}; +use crate::{DATA_DIR, PACKAGE_DATA, PackageId}; pub async fn cleanup(ctx: &RpcContext, id: &PackageId, soft: bool) -> Result<(), Error> { Ok( diff --git a/core/startos/src/sign/mod.rs b/core/startos/src/sign/mod.rs index a685a0e62..f0ed1e786 100644 --- a/core/startos/src/sign/mod.rs +++ b/core/startos/src/sign/mod.rs @@ -4,7 +4,6 @@ use std::str::FromStr; use ::ed25519::pkcs8::BitStringRef; use clap::builder::ValueParserFactory; use der::referenced::OwnedToRef; -use crate::util::FromStrParser; use pkcs8::der::AnyRef; use pkcs8::{PrivateKeyInfo, SubjectPublicKeyInfo}; use serde::{Deserialize, Serialize}; @@ -14,6 +13,7 @@ use ts_rs::TS; use crate::prelude::*; use crate::sign::commitment::Digestable; use crate::sign::ed25519::Ed25519; +use crate::util::FromStrParser; use crate::util::serde::{deserialize_from_str, serialize_display}; pub mod commitment; diff --git a/core/startos/src/ssh.rs b/core/startos/src/ssh.rs index c1bef01b7..cec4b0daf 100644 --- a/core/startos/src/ssh.rs +++ b/core/startos/src/ssh.rs @@ -4,7 +4,6 @@ use std::path::Path; use clap::Parser; use clap::builder::ValueParserFactory; use imbl_value::InternedString; -use crate::util::FromStrParser; use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::fs::OpenOptions; @@ -15,9 +14,9 @@ use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::hostname::Hostname; use crate::prelude::*; -use crate::util::Invoke; use crate::util::io::create_file; use crate::util::serde::{HandlerExtSerde, Pem, WithIoFormat, display_serializable}; +use crate::util::{FromStrParser, Invoke}; pub const SSH_DIR: &str = "/home/start9/.ssh"; diff --git a/core/startos/src/status/health_check.rs b/core/startos/src/status/health_check.rs index 8807d7a6c..d028987dd 100644 --- a/core/startos/src/status/health_check.rs +++ b/core/startos/src/status/health_check.rs @@ -1,11 +1,12 @@ use std::str::FromStr; use clap::builder::ValueParserFactory; -use crate::util::FromStrParser; -pub use crate::HealthCheckId; use serde::{Deserialize, Serialize}; use ts_rs::TS; +pub use crate::HealthCheckId; +use crate::util::FromStrParser; + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, TS)] #[serde(rename_all = "camelCase")] pub struct NamedHealthCheckResult { diff --git a/core/startos/src/status/mod.rs b/core/startos/src/status/mod.rs index 9bdb987d0..05efab83f 100644 --- a/core/startos/src/status/mod.rs +++ b/core/startos/src/status/mod.rs @@ -1,11 +1,11 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; -use crate::error::ErrorData; -use crate::HealthCheckId; use serde::{Deserialize, Serialize}; use ts_rs::TS; +use crate::HealthCheckId; +use crate::error::ErrorData; use crate::prelude::*; use crate::service::start_stop::StartStop; use crate::status::health_check::NamedHealthCheckResult; diff --git a/core/startos/src/tunnel/api.rs b/core/startos/src/tunnel/api.rs index 0e32f5452..ac6e1cdf3 100644 --- a/core/startos/src/tunnel/api.rs +++ b/core/startos/src/tunnel/api.rs @@ -7,9 +7,11 @@ use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_f use serde::{Deserialize, Serialize}; use crate::context::CliContext; +use crate::db::model::public::NetworkInterfaceType; +use crate::net::forward::add_iptables_rule; use crate::prelude::*; use crate::tunnel::context::TunnelContext; -use crate::tunnel::wg::{WgConfig, WgSubnetClients, WgSubnetConfig}; +use crate::tunnel::wg::{WIREGUARD_INTERFACE_NAME, WgConfig, WgSubnetClients, WgSubnetConfig}; use crate::util::serde::{HandlerExtSerde, display_serializable}; pub fn tunnel_api() -> ParentHandler { @@ -167,7 +169,37 @@ pub async fn add_subnet( }) .await .result?; - server.sync().await + server.sync().await?; + + for iface in ctx.net_iface.peek(|i| { + i.iter() + .filter(|(_, info)| { + info.ip_info.as_ref().map_or(false, |i| { + i.device_type != Some(NetworkInterfaceType::Loopback) + }) + }) + .map(|(name, _)| name) + .filter(|id| id.as_str() != WIREGUARD_INTERFACE_NAME) + .cloned() + .collect::>() + }) { + add_iptables_rule( + true, + false, + &[ + "POSTROUTING", + "-s", + &subnet.trunc().to_string(), + "-o", + iface.as_str(), + "-j", + "MASQUERADE", + ], + ) + .await?; + } + + Ok(()) } pub async fn remove_subnet( @@ -184,7 +216,37 @@ pub async fn remove_subnet( .await .result?; server.sync().await?; - ctx.gc_forwards(&keep).await + ctx.gc_forwards(&keep).await?; + + for iface in ctx.net_iface.peek(|i| { + i.iter() + .filter(|(_, info)| { + info.ip_info.as_ref().map_or(false, |i| { + i.device_type != Some(NetworkInterfaceType::Loopback) + }) + }) + .map(|(name, _)| name) + .filter(|id| id.as_str() != WIREGUARD_INTERFACE_NAME) + .cloned() + .collect::>() + }) { + add_iptables_rule( + true, + true, + &[ + "POSTROUTING", + "-s", + &subnet.trunc().to_string(), + "-o", + iface.as_str(), + "-j", + "MASQUERADE", + ], + ) + .await?; + } + + Ok(()) } #[derive(Deserialize, Serialize, Parser)] diff --git a/core/startos/src/tunnel/auth.rs b/core/startos/src/tunnel/auth.rs index 8e4003a6e..713d038a3 100644 --- a/core/startos/src/tunnel/auth.rs +++ b/core/startos/src/tunnel/auth.rs @@ -9,8 +9,10 @@ use ts_rs::TS; use crate::auth::{Sessions, check_password}; use crate::context::CliContext; -use crate::middleware::auth::AuthContext; -use crate::middleware::signature::SignatureAuthContext; +use crate::middleware::auth::DbContext; +use crate::middleware::auth::local::LocalAuthContext; +use crate::middleware::auth::session::SessionAuthContext; +use crate::middleware::auth::signature::SignatureAuthContext; use crate::prelude::*; use crate::rpc_continuations::OpenAuthedContinuations; use crate::sign::AnyVerifyingKey; @@ -19,13 +21,15 @@ use crate::tunnel::db::TunnelDatabase; use crate::util::serde::{HandlerExtSerde, display_serializable}; use crate::util::sync::SyncMutex; -impl SignatureAuthContext for TunnelContext { +impl DbContext for TunnelContext { type Database = TunnelDatabase; - type AdditionalMetadata = (); - type CheckPubkeyRes = (); fn db(&self) -> &TypedPatchDb { &self.db } +} +impl SignatureAuthContext for TunnelContext { + type AdditionalMetadata = (); + type CheckPubkeyRes = (); async fn sig_context( &self, ) -> impl IntoIterator + Send, Error>> + Send { @@ -93,9 +97,11 @@ impl SignatureAuthContext for TunnelContext { Ok(()) } } -impl AuthContext for TunnelContext { - const LOCAL_AUTH_COOKIE_PATH: &str = "/run/start-tunnel/rpc.authcookie"; +impl LocalAuthContext for TunnelContext { + const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/tunnel.authcookie"; const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:root"; +} +impl SessionAuthContext for TunnelContext { fn access_sessions(db: &mut Model) -> &mut Model { db.as_sessions_mut() } diff --git a/core/startos/src/tunnel/context.rs b/core/startos/src/tunnel/context.rs index 0e426f481..daa2164ec 100644 --- a/core/startos/src/tunnel/context.rs +++ b/core/startos/src/tunnel/context.rs @@ -11,23 +11,23 @@ use imbl::OrdMap; use imbl_value::InternedString; use include_dir::Dir; use ipnet::Ipv4Net; -use crate::GatewayId; use patch_db::PatchDb; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{CallRemote, Context, Empty, ParentHandler}; use serde::{Deserialize, Serialize}; -use tokio::process::Command; use tokio::sync::broadcast::Sender; use tracing::instrument; use url::Url; +use crate::GatewayId; use crate::auth::Sessions; use crate::context::config::ContextConfig; use crate::context::{CliContext, RpcContext}; use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType}; -use crate::middleware::auth::{Auth, AuthContext}; +use crate::middleware::auth::Auth; +use crate::middleware::auth::local::LocalAuthContext; use crate::middleware::cors::Cors; -use crate::net::forward::PortForwardController; +use crate::net::forward::{PortForwardController, add_iptables_rule}; use crate::net::static_server::{EMPTY_DIR, UiContext}; use crate::prelude::*; use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations}; @@ -35,7 +35,6 @@ use crate::tunnel::TUNNEL_DEFAULT_LISTEN; use crate::tunnel::api::tunnel_api; use crate::tunnel::db::TunnelDatabase; use crate::tunnel::wg::{WIREGUARD_INTERFACE_NAME, WgSubnetConfig}; -use crate::util::Invoke; use crate::util::collections::OrdMapIterMut; use crate::util::io::read_file_to_string; use crate::util::sync::{SyncMutex, Watch}; @@ -134,12 +133,25 @@ impl TunnelContext { .result?; let net_iface = Watch::new(net_iface); let forward = PortForwardController::new(); + add_iptables_rule( + false, + false, + &[ + "FORWARD", + "-i", + WIREGUARD_INTERFACE_NAME, + "-m", + "state", + "--state", + "NEW", + "-j", + "ACCEPT", + ], + ) + .await?; - Command::new("sysctl") - .arg("-w") - .arg("net.ipv4.ip_forward=1") - .invoke(ErrorKind::Network) - .await?; + let peek = db.peek().await; + peek.as_wg().de()?.sync().await?; for iface in net_iface.peek(|i| { i.iter() @@ -153,37 +165,24 @@ impl TunnelContext { .cloned() .collect::>() }) { - if Command::new("iptables") - .arg("-t") - .arg("nat") - .arg("-C") - .arg("POSTROUTING") - .arg("-o") - .arg(iface.as_str()) - .arg("-j") - .arg("MASQUERADE") - .invoke(ErrorKind::Network) - .await - .is_err() - { - tracing::info!("Adding masquerade rule for interface {}", iface); - Command::new("iptables") - .arg("-t") - .arg("nat") - .arg("-A") - .arg("POSTROUTING") - .arg("-o") - .arg(iface.as_str()) - .arg("-j") - .arg("MASQUERADE") - .invoke(ErrorKind::Network) - .await - .log_err(); + for subnet in peek.as_wg().as_subnets().keys()? { + add_iptables_rule( + true, + false, + &[ + "POSTROUTING", + "-s", + &subnet.trunc().to_string(), + "-o", + iface.as_str(), + "-j", + "MASQUERADE", + ], + ) + .await?; } } - let peek = db.peek().await; - peek.as_wg().de()?.sync().await?; let mut active_forwards = BTreeMap::new(); for (from, to) in peek.as_port_forwards().de()?.0 { active_forwards.insert(from, forward.add_forward(from, to).await?); @@ -281,7 +280,7 @@ impl CallRemote for CliContext { method = method.strip_prefix("tunnel.").unwrap_or(method); - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, url, HeaderMap::new(), @@ -310,7 +309,7 @@ impl CallRemote for RpcContext { let sig_ctx = url.host_str().map(InternedString::from_display); - crate::middleware::signature::call_remote( + crate::middleware::auth::signature::call_remote( self, url, HeaderMap::new(), @@ -333,6 +332,11 @@ impl UiContext for TunnelContext { tunnel_api() } fn middleware(server: rpc_toolkit::Server) -> rpc_toolkit::HttpServer { - server.middleware(Cors::new()).middleware(Auth::new()) + server.middleware(Cors::new()).middleware( + Auth::new() + .with_local_auth() + .with_signature_auth() + .with_session_auth(), + ) } } diff --git a/core/startos/src/tunnel/db.rs b/core/startos/src/tunnel/db.rs index 97353230b..863943543 100644 --- a/core/startos/src/tunnel/db.rs +++ b/core/startos/src/tunnel/db.rs @@ -8,7 +8,6 @@ use clap::Parser; use imbl::{HashMap, OrdMap}; use imbl_value::InternedString; use itertools::Itertools; -use crate::GatewayId; use patch_db::Dump; use patch_db::json_ptr::{JsonPointer, ROOT}; use rpc_toolkit::yajrc::RpcError; @@ -17,6 +16,7 @@ use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; +use crate::GatewayId; use crate::auth::Sessions; use crate::context::CliContext; use crate::db::model::public::NetworkInterfaceInfo; diff --git a/core/startos/src/tunnel/web.rs b/core/startos/src/tunnel/web.rs index 662752d0b..35d18f643 100644 --- a/core/startos/src/tunnel/web.rs +++ b/core/startos/src/tunnel/web.rs @@ -289,16 +289,16 @@ pub async fn generate_certificate( ) -> Result>, Error> { let saninfo = SANInfo::new(&subject.into_iter().collect()); - let root_key = crate::net::ssl::generate_key()?; + let root_key = crate::net::ssl::gen_nistp256()?; let root_cert = crate::net::ssl::make_root_cert( &root_key, &Hostname("start-tunnel".into()), root_ca_start_time().await, )?; - let int_key = crate::net::ssl::generate_key()?; + let int_key = crate::net::ssl::gen_nistp256()?; let int_cert = crate::net::ssl::make_int_cert((&root_key, &root_cert), &int_key)?; - let key = crate::net::ssl::generate_key()?; + let key = crate::net::ssl::gen_nistp256()?; let cert = crate::net::ssl::make_leaf_cert((&int_key, &int_cert), (&key, &saninfo))?; let chain = Pem(vec![cert, int_cert, root_cert]); diff --git a/core/startos/src/tunnel/wg.rs b/core/startos/src/tunnel/wg.rs index d9c9704ae..8696ba293 100644 --- a/core/startos/src/tunnel/wg.rs +++ b/core/startos/src/tunnel/wg.rs @@ -228,10 +228,7 @@ impl std::fmt::Display for ClientConfig { name = self.client_config.name, privkey = self.client_config.key.to_padded_string(), psk = self.client_config.psk.to_padded_string(), - addr = Ipv4Net::new_assert( - self.client_addr, - self.subnet.prefix_len() - ), + addr = Ipv4Net::new_assert(self.client_addr, self.subnet.prefix_len()), subnet = self.subnet.trunc(), server_pubkey = self.server_pubkey.to_padded_string(), server_addr = self.server_addr, diff --git a/core/startos/src/update/mod.rs b/core/startos/src/update/mod.rs index 0f0923036..44d67e3c9 100644 --- a/core/startos/src/update/mod.rs +++ b/core/startos/src/update/mod.rs @@ -6,7 +6,6 @@ use clap::{ArgAction, Parser}; use color_eyre::eyre::{Result, eyre}; use exver::{Version, VersionRange}; use futures::TryStreamExt; -use crate::util::future::NonDetachingJoinHandle; use imbl_value::json; use itertools::Itertools; use patch_db::json_ptr::JsonPointer; @@ -36,6 +35,7 @@ use crate::sound::{ CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4, }; use crate::util::Invoke; +use crate::util::future::NonDetachingJoinHandle; use crate::util::io::AtomicFile; use crate::util::net::WebSocketExt; diff --git a/core/startos/src/util/actor/concurrent.rs b/core/startos/src/util/actor/concurrent.rs index 111106726..cce7b691b 100644 --- a/core/startos/src/util/actor/concurrent.rs +++ b/core/startos/src/util/actor/concurrent.rs @@ -4,13 +4,13 @@ use std::time::Duration; use futures::future::{BoxFuture, ready}; use futures::{Future, FutureExt, TryFutureExt}; -use crate::util::future::NonDetachingJoinHandle; use tokio::sync::{mpsc, oneshot}; use crate::prelude::*; use crate::rpc_continuations::Guid; use crate::util::actor::background::{BackgroundJobQueue, BackgroundJobRunner}; use crate::util::actor::{Actor, ConflictFn, Handler, PendingMessageStrategy, Request}; +use crate::util::future::NonDetachingJoinHandle; #[pin_project::pin_project] struct ConcurrentRunner { diff --git a/core/startos/src/util/actor/simple.rs b/core/startos/src/util/actor/simple.rs index e92e8e149..663a1a3bd 100644 --- a/core/startos/src/util/actor/simple.rs +++ b/core/startos/src/util/actor/simple.rs @@ -2,7 +2,6 @@ use std::time::Duration; use futures::future::ready; use futures::{Future, FutureExt, TryFutureExt}; -use crate::util::future::NonDetachingJoinHandle; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::{mpsc, oneshot}; @@ -10,6 +9,7 @@ use crate::prelude::*; use crate::rpc_continuations::Guid; use crate::util::actor::background::BackgroundJobQueue; use crate::util::actor::{Actor, Handler, PendingMessageStrategy, Request}; +use crate::util::future::NonDetachingJoinHandle; pub struct SimpleActor { shutdown: oneshot::Sender<()>, diff --git a/core/startos/src/util/data_url.rs b/core/startos/src/util/data_url.rs index 008ae5025..20a0060aa 100644 --- a/core/startos/src/util/data_url.rs +++ b/core/startos/src/util/data_url.rs @@ -4,11 +4,11 @@ use std::str::FromStr; use base64::Engine; use color_eyre::eyre::eyre; +use imbl_value::InternedString; use reqwest::header::CONTENT_TYPE; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncRead, AsyncReadExt}; use ts_rs::TS; -use imbl_value::InternedString; use crate::util::mime::{mime, unmime}; use crate::{Error, ErrorKind, ResultExt}; diff --git a/core/startos/src/util/io.rs b/core/startos/src/util/io.rs index 10ffd4476..fa6ea9414 100644 --- a/core/startos/src/util/io.rs +++ b/core/startos/src/util/io.rs @@ -16,7 +16,6 @@ use clap::builder::ValueParserFactory; use futures::future::{BoxFuture, Fuse}; use futures::{FutureExt, Stream, TryStreamExt}; use inotify::{EventMask, EventStream, Inotify, WatchMask}; -use crate::util::FromStrParser; use nix::unistd::{Gid, Uid}; use serde::{Deserialize, Serialize}; use tokio::fs::{File, OpenOptions}; @@ -30,6 +29,7 @@ use tokio::time::{Instant, Sleep}; use ts_rs::TS; use crate::prelude::*; +use crate::util::FromStrParser; use crate::util::future::NonDetachingJoinHandle; use crate::util::sync::SyncMutex; diff --git a/core/startos/src/util/lshw.rs b/core/startos/src/util/lshw.rs index 425619a11..98c1a09ab 100644 --- a/core/startos/src/util/lshw.rs +++ b/core/startos/src/util/lshw.rs @@ -1,9 +1,9 @@ -use crate::{Error, ResultExt}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; use crate::util::Invoke; +use crate::{Error, ResultExt}; const KNOWN_CLASSES: &[&str] = &["processor", "display"]; diff --git a/core/startos/src/util/mod.rs b/core/startos/src/util/mod.rs index df8cefb63..3a351bd87 100644 --- a/core/startos/src/util/mod.rs +++ b/core/startos/src/util/mod.rs @@ -35,16 +35,16 @@ use crate::{Error, ErrorKind, ResultExt as _}; pub mod actor; pub mod clap; pub mod collections; -pub mod data_url; -pub mod mime; pub mod cpupower; pub mod crypto; +pub mod data_url; pub mod future; pub mod http_reader; pub mod io; pub mod iter; pub mod logger; pub mod lshw; +pub mod mime; pub mod net; pub mod rpc; pub mod rpc_client; diff --git a/core/startos/src/util/rpc_client.rs b/core/startos/src/util/rpc_client.rs index e80681bde..3dd6e6b21 100644 --- a/core/startos/src/util/rpc_client.rs +++ b/core/startos/src/util/rpc_client.rs @@ -6,7 +6,6 @@ use std::sync::{Arc, Weak}; use futures::future::BoxFuture; use futures::{FutureExt, TryFutureExt}; use lazy_async_pool::Pool; -use crate::{Error, ErrorKind, ResultExt}; use rpc_toolkit::yajrc::{self, Id, RpcError, RpcMethod, RpcRequest, RpcResponse}; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; @@ -17,6 +16,7 @@ use tokio::sync::{Mutex, OnceCell, oneshot}; use crate::util::future::NonDetachingJoinHandle; use crate::util::io::TmpDir; +use crate::{Error, ErrorKind, ResultExt}; type DynWrite = Box; type ResponseMap = BTreeMap>>; diff --git a/core/startos/src/util/rsync.rs b/core/startos/src/util/rsync.rs index fcaf11b04..1e91f77dc 100644 --- a/core/startos/src/util/rsync.rs +++ b/core/startos/src/util/rsync.rs @@ -2,7 +2,6 @@ use std::path::Path; use color_eyre::eyre::eyre; use futures::StreamExt; -use crate::{Error, ErrorKind}; use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use tokio::process::{Child, Command}; use tokio::sync::watch; @@ -11,6 +10,7 @@ use tokio_stream::wrappers::WatchStream; use crate::util::future::NonDetachingJoinHandle; use crate::util::io::ByteReplacementReader; use crate::util::serde::const_true; +use crate::{Error, ErrorKind}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/util/serde.rs b/core/startos/src/util/serde.rs index 82229d95b..286041822 100644 --- a/core/startos/src/util/serde.rs +++ b/core/startos/src/util/serde.rs @@ -8,7 +8,6 @@ use clap::builder::ValueParserFactory; use clap::{ArgMatches, CommandFactory, FromArgMatches}; use color_eyre::eyre::eyre; use imbl_value::imbl::OrdMap; -use crate::util::FromStrParser; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; use rpc_toolkit::{ @@ -21,7 +20,7 @@ use ts_rs::TS; use super::IntoDoubleEndedIterator; use crate::prelude::*; -use crate::util::Apply; +use crate::util::{Apply, FromStrParser}; pub fn const_true() -> bool { true diff --git a/core/startos/src/util/sync.rs b/core/startos/src/util/sync.rs index 3f97e913f..0f63e735a 100644 --- a/core/startos/src/util/sync.rs +++ b/core/startos/src/util/sync.rs @@ -51,7 +51,7 @@ where }) { panic!("lock {id} is already locked on this thread"); } - let tracer: helpers::NonDetachingJoinHandle<()> = { + let tracer: crate::util::future::NonDetachingJoinHandle<()> = { let bt = std::backtrace::Backtrace::force_capture(); tokio::spawn(async move { use std::time::Duration; diff --git a/core/startos/src/version/v0_3_6_alpha_0.rs b/core/startos/src/version/v0_3_6_alpha_0.rs index 9ddf7c067..3840721bb 100644 --- a/core/startos/src/version/v0_3_6_alpha_0.rs +++ b/core/startos/src/version/v0_3_6_alpha_0.rs @@ -7,7 +7,6 @@ use const_format::formatcp; use ed25519_dalek::SigningKey; use exver::{PreReleaseSegment, VersionRange}; use imbl_value::{InternedString, json}; -use crate::{HostId, Id, PackageId, ReplayId}; use openssl::pkey::PKey; use openssl::x509::X509; use sqlx::postgres::PgConnectOptions; @@ -34,7 +33,7 @@ use crate::ssh::{SshKeys, SshPubKey}; use crate::util::Invoke; use crate::util::crypto::ed25519_expand_key; use crate::util::serde::Pem; -use crate::{DATA_DIR, PACKAGE_DATA}; +use crate::{DATA_DIR, HostId, Id, PACKAGE_DATA, PackageId, ReplayId}; lazy_static::lazy_static! { static ref V0_3_6_alpha_0: exver::Version = exver::Version::new( diff --git a/core/startos/src/version/v0_3_6_alpha_10.rs b/core/startos/src/version/v0_3_6_alpha_10.rs index a2a2fbf28..08543d9e1 100644 --- a/core/startos/src/version/v0_3_6_alpha_10.rs +++ b/core/startos/src/version/v0_3_6_alpha_10.rs @@ -2,11 +2,11 @@ use std::collections::{BTreeMap, BTreeSet}; use exver::{PreReleaseSegment, VersionRange}; use imbl_value::InternedString; -use crate::GatewayId; use serde::{Deserialize, Serialize}; use super::v0_3_5::V0_3_0_COMPAT; use super::{VersionT, v0_3_6_alpha_9}; +use crate::GatewayId; use crate::net::host::address::PublicDomainConfig; use crate::net::tor::OnionAddress; use crate::prelude::*; diff --git a/core/startos/src/version/v0_4_0_alpha_12.rs b/core/startos/src/version/v0_4_0_alpha_12.rs index 5ff6cd4d2..fa7e5a189 100644 --- a/core/startos/src/version/v0_4_0_alpha_12.rs +++ b/core/startos/src/version/v0_4_0_alpha_12.rs @@ -4,7 +4,7 @@ use exver::{PreReleaseSegment, VersionRange}; use imbl_value::InternedString; use super::v0_3_5::V0_3_0_COMPAT; -use super::{v0_4_0_alpha_11, VersionT}; +use super::{VersionT, v0_4_0_alpha_11}; use crate::net::tor::TorSecretKey; use crate::prelude::*; diff --git a/core/startos/src/version/v0_4_0_alpha_9.rs b/core/startos/src/version/v0_4_0_alpha_9.rs index 4f047bd74..ce7bf399c 100644 --- a/core/startos/src/version/v0_4_0_alpha_9.rs +++ b/core/startos/src/version/v0_4_0_alpha_9.rs @@ -4,16 +4,15 @@ use std::sync::Arc; use exver::{PreReleaseSegment, VersionRange}; use imbl_value::{InOMap, InternedString}; -use crate::PackageId; use super::v0_3_5::V0_3_0_COMPAT; use super::{VersionT, v0_4_0_alpha_8}; -use crate::DATA_DIR; use crate::context::RpcContext; use crate::install::PKG_ARCHIVE_DIR; use crate::prelude::*; use crate::util::io::write_file_atomic; use crate::volume::PKG_VOLUME_DIR; +use crate::{DATA_DIR, PackageId}; lazy_static::lazy_static! { static ref V0_4_0_alpha_9: exver::Version = exver::Version::new( diff --git a/core/startos/src/volume.rs b/core/startos/src/volume.rs index be171fd65..2755fd809 100644 --- a/core/startos/src/volume.rs +++ b/core/startos/src/volume.rs @@ -2,7 +2,6 @@ use std::path::{Path, PathBuf}; use crate::PackageId; pub use crate::VolumeId; - use crate::prelude::*; use crate::util::VersionString; diff --git a/core/startos/start-registryd.service b/core/startos/start-registryd.service index 4103808ac..e8e6390ba 100644 --- a/core/startos/start-registryd.service +++ b/core/startos/start-registryd.service @@ -3,7 +3,7 @@ Description=StartOS Registry [Service] Type=simple -Environment=RUST_LOG=startos=debug,patch_db=warn,models=debug +Environment=RUST_LOG=startos=debug,patch_db=warn ExecStart=/usr/bin/start-registryd Restart=always RestartSec=3 diff --git a/core/startos/start-tunneld.service b/core/startos/start-tunneld.service index 7ed3ed17a..b0d0a2043 100644 --- a/core/startos/start-tunneld.service +++ b/core/startos/start-tunneld.service @@ -3,7 +3,7 @@ Description=StartTunnel [Service] Type=simple -Environment=RUST_LOG=startos=debug,patch_db=warn,models=debug +Environment=RUST_LOG=startos=debug,patch_db=warn ExecStart=/usr/bin/start-tunneld Restart=always RestartSec=3 diff --git a/core/startos/startd.service b/core/startos/startd.service index 4a8f81810..6ce17697e 100644 --- a/core/startos/startd.service +++ b/core/startos/startd.service @@ -3,7 +3,7 @@ Description=StartOS Daemon [Service] Type=simple -Environment=RUST_LOG=startos=debug,patch_db=warn,models=debug +Environment=RUST_LOG=startos=debug,patch_db=warn ExecStart=/usr/bin/startd Restart=always RestartSec=3 diff --git a/sdk/base/lib/util/deepEqual.ts b/sdk/base/lib/util/deepEqual.ts index 8e6ba4b65..7a1d534cc 100644 --- a/sdk/base/lib/util/deepEqual.ts +++ b/sdk/base/lib/util/deepEqual.ts @@ -1,13 +1,13 @@ import { object } from "ts-matches" export function deepEqual(...args: unknown[]) { - if (!object.test(args[args.length - 1])) return args[args.length - 1] const objects = args.filter(object.test) if (objects.length === 0) { for (const x of args) if (x !== args[0]) return false return true } if (objects.length !== args.length) return false + if (objects.some(Array.isArray) && !objects.every(Array.isArray)) return false const allKeys = new Set(objects.flatMap((x) => Object.keys(x))) for (const key of allKeys) { for (const x of objects) { diff --git a/sdk/base/lib/util/getServiceInterface.ts b/sdk/base/lib/util/getServiceInterface.ts index b9ebb6296..97196cd1c 100644 --- a/sdk/base/lib/util/getServiceInterface.ts +++ b/sdk/base/lib/util/getServiceInterface.ts @@ -1,9 +1,10 @@ -import { ServiceInterfaceType } from "../types" +import { PackageId, ServiceInterfaceId, ServiceInterfaceType } from "../types" import { knownProtocols } from "../interfaces/Host" import { AddressInfo, Host, Hostname, HostnameInfo } from "../types" import { Effects } from "../Effects" import { DropGenerator, DropPromise } from "./Drop" import { IpAddress, IPV6_LINK_LOCAL } from "./ip" +import { deepEqual } from "./deepEqual" export type UrlString = string export type HostId = string @@ -227,7 +228,7 @@ function filterRec( (kind.has("ipv4") && h.kind === "ip" && h.hostname.kind === "ipv4") || (kind.has("ipv6") && h.kind === "ip" && h.hostname.kind === "ipv6") || (kind.has("localhost") && - ["localhost", "127.0.0.1", "[::1]"].includes(h.hostname.value)) || + ["localhost", "127.0.0.1", "::1"].includes(h.hostname.value)) || (kind.has("link-local") && h.kind === "ip" && h.hostname.kind === "ipv6" && @@ -328,28 +329,28 @@ const makeInterfaceFilled = async ({ return interfaceFilled } -export class GetServiceInterface { +export class GetServiceInterface { constructor( readonly effects: Effects, readonly opts: { id: string; packageId?: string }, + readonly map: (interfaces: ServiceInterfaceFilled | null) => Mapped, + readonly eq: (a: Mapped, b: Mapped) => boolean, ) {} /** * Returns the requested service interface. Reruns the context from which it has been called if the underlying value changes */ async const() { - const { id, packageId } = this.opts - const callback = - this.effects.constRetry && - (() => this.effects.constRetry && this.effects.constRetry()) - const interfaceFilled = await makeInterfaceFilled({ - effects: this.effects, - id, - packageId, - callback, - }) - - return interfaceFilled + let abort = new AbortController() + const watch = this.watch(abort.signal) + const res = await watch.next() + if (this.effects.constRetry) { + watch.next().then(() => { + abort.abort() + this.effects.constRetry && this.effects.constRetry() + }) + } + return res.value } /** * Returns the requested service interface. Does nothing if the value changes @@ -362,10 +363,11 @@ export class GetServiceInterface { packageId, }) - return interfaceFilled + return this.map(interfaceFilled) } private async *watchGen(abort?: AbortSignal) { + let prev = null as { value: Mapped } | null const { id, packageId } = this.opts const resolveCell = { resolve: () => {} } this.effects.onLeaveContext(() => { @@ -378,12 +380,17 @@ export class GetServiceInterface { callback = resolve resolveCell.resolve = resolve }) - yield await makeInterfaceFilled({ - effects: this.effects, - id, - packageId, - callback, - }) + const next = this.map( + await makeInterfaceFilled({ + effects: this.effects, + id, + packageId, + callback, + }), + ) + if (!prev || !this.eq(prev.value, next)) { + yield next + } await waitForNext } } @@ -391,9 +398,7 @@ export class GetServiceInterface { /** * Watches the requested service interface. Returns an async iterator that yields whenever the value changes */ - watch( - abort?: AbortSignal, - ): AsyncGenerator { + watch(abort?: AbortSignal): AsyncGenerator { const ctrl = new AbortController() abort?.addEventListener("abort", () => ctrl.abort()) return DropGenerator.of(this.watchGen(ctrl.signal), () => ctrl.abort()) @@ -404,7 +409,7 @@ export class GetServiceInterface { */ onChange( callback: ( - value: ServiceInterfaceFilled | null, + value: Mapped | null, error?: Error, ) => { cancel: boolean } | Promise<{ cancel: boolean }>, ) { @@ -437,9 +442,7 @@ export class GetServiceInterface { /** * Watches the requested service interface. Returns when the predicate is true */ - waitFor( - pred: (value: ServiceInterfaceFilled | null) => boolean, - ): Promise { + waitFor(pred: (value: Mapped) => boolean): Promise { const ctrl = new AbortController() return DropPromise.of( Promise.resolve().then(async () => { @@ -448,15 +451,57 @@ export class GetServiceInterface { return next } } - return null + throw new Error("context left before predicate passed") }), () => ctrl.abort(), ) } } + +export function getOwnServiceInterface( + effects: Effects, + id: ServiceInterfaceId, +): GetServiceInterface +export function getOwnServiceInterface( + effects: Effects, + id: ServiceInterfaceId, + map: (interfaces: ServiceInterfaceFilled | null) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterface +export function getOwnServiceInterface( + effects: Effects, + id: ServiceInterfaceId, + map?: (interfaces: ServiceInterfaceFilled | null) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterface { + return new GetServiceInterface( + effects, + { id }, + map ?? ((a) => a as Mapped), + eq ?? ((a, b) => deepEqual(a, b)), + ) +} + export function getServiceInterface( effects: Effects, - opts: { id: string; packageId?: string }, -) { - return new GetServiceInterface(effects, opts) + opts: { id: ServiceInterfaceId; packageId: PackageId }, +): GetServiceInterface +export function getServiceInterface( + effects: Effects, + opts: { id: ServiceInterfaceId; packageId: PackageId }, + map: (interfaces: ServiceInterfaceFilled | null) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterface +export function getServiceInterface( + effects: Effects, + opts: { id: ServiceInterfaceId; packageId: PackageId }, + map?: (interfaces: ServiceInterfaceFilled | null) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterface { + return new GetServiceInterface( + effects, + opts, + map ?? ((a) => a as Mapped), + eq ?? ((a, b) => deepEqual(a, b)), + ) } diff --git a/sdk/base/lib/util/getServiceInterfaces.ts b/sdk/base/lib/util/getServiceInterfaces.ts index 34636981a..b4bf8fc39 100644 --- a/sdk/base/lib/util/getServiceInterfaces.ts +++ b/sdk/base/lib/util/getServiceInterfaces.ts @@ -1,4 +1,6 @@ import { Effects } from "../Effects" +import { PackageId } from "../osBindings" +import { deepEqual } from "./deepEqual" import { DropGenerator, DropPromise } from "./Drop" import { ServiceInterfaceFilled, @@ -41,28 +43,28 @@ const makeManyInterfaceFilled = async ({ return serviceInterfacesFilled } -export class GetServiceInterfaces { +export class GetServiceInterfaces { constructor( readonly effects: Effects, readonly opts: { packageId?: string }, + readonly map: (interfaces: ServiceInterfaceFilled[]) => Mapped, + readonly eq: (a: Mapped, b: Mapped) => boolean, ) {} /** * Returns the service interfaces for the package. Reruns the context from which it has been called if the underlying value changes */ async const() { - const { packageId } = this.opts - const callback = - this.effects.constRetry && - (() => this.effects.constRetry && this.effects.constRetry()) - const interfaceFilled: ServiceInterfaceFilled[] = - await makeManyInterfaceFilled({ - effects: this.effects, - packageId, - callback, + let abort = new AbortController() + const watch = this.watch(abort.signal) + const res = await watch.next() + if (this.effects.constRetry) { + watch.next().then(() => { + abort.abort() + this.effects.constRetry && this.effects.constRetry() }) - - return interfaceFilled + } + return res.value } /** * Returns the service interfaces for the package. Does nothing if the value changes @@ -75,10 +77,11 @@ export class GetServiceInterfaces { packageId, }) - return interfaceFilled + return this.map(interfaceFilled) } private async *watchGen(abort?: AbortSignal) { + let prev = null as { value: Mapped } | null const { packageId } = this.opts const resolveCell = { resolve: () => {} } this.effects.onLeaveContext(() => { @@ -91,11 +94,16 @@ export class GetServiceInterfaces { callback = resolve resolveCell.resolve = resolve }) - yield await makeManyInterfaceFilled({ - effects: this.effects, - packageId, - callback, - }) + const next = this.map( + await makeManyInterfaceFilled({ + effects: this.effects, + packageId, + callback, + }), + ) + if (!prev || !this.eq(prev.value, next)) { + yield next + } await waitForNext } } @@ -103,9 +111,7 @@ export class GetServiceInterfaces { /** * Watches the service interfaces for the package. Returns an async iterator that yields whenever the value changes */ - watch( - abort?: AbortSignal, - ): AsyncGenerator { + watch(abort?: AbortSignal): AsyncGenerator { const ctrl = new AbortController() abort?.addEventListener("abort", () => ctrl.abort()) return DropGenerator.of(this.watchGen(ctrl.signal), () => ctrl.abort()) @@ -116,7 +122,7 @@ export class GetServiceInterfaces { */ onChange( callback: ( - value: ServiceInterfaceFilled[] | null, + value: Mapped | null, error?: Error, ) => { cancel: boolean } | Promise<{ cancel: boolean }>, ) { @@ -149,9 +155,7 @@ export class GetServiceInterfaces { /** * Watches the service interfaces for the package. Returns when the predicate is true */ - waitFor( - pred: (value: ServiceInterfaceFilled[] | null) => boolean, - ): Promise { + waitFor(pred: (value: Mapped) => boolean): Promise { const ctrl = new AbortController() return DropPromise.of( Promise.resolve().then(async () => { @@ -160,15 +164,52 @@ export class GetServiceInterfaces { return next } } - return null + throw new Error("context left before predicate passed") }), () => ctrl.abort(), ) } } + +export function getOwnServiceInterfaces(effects: Effects): GetServiceInterfaces +export function getOwnServiceInterfaces( + effects: Effects, + map: (interfaces: ServiceInterfaceFilled[]) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterfaces +export function getOwnServiceInterfaces( + effects: Effects, + map?: (interfaces: ServiceInterfaceFilled[]) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterfaces { + return new GetServiceInterfaces( + effects, + {}, + map ?? ((a) => a as Mapped), + eq ?? ((a, b) => deepEqual(a, b)), + ) +} + export function getServiceInterfaces( effects: Effects, - opts: { packageId?: string }, -) { - return new GetServiceInterfaces(effects, opts) + opts: { packageId: PackageId }, +): GetServiceInterfaces +export function getServiceInterfaces( + effects: Effects, + opts: { packageId: PackageId }, + map: (interfaces: ServiceInterfaceFilled[]) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterfaces +export function getServiceInterfaces( + effects: Effects, + opts: { packageId: PackageId }, + map?: (interfaces: ServiceInterfaceFilled[]) => Mapped, + eq?: (a: Mapped, b: Mapped) => boolean, +): GetServiceInterfaces { + return new GetServiceInterfaces( + effects, + opts, + map ?? ((a) => a as Mapped), + eq ?? ((a, b) => deepEqual(a, b)), + ) } diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index 8510b251f..6fef55afb 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -60,6 +60,11 @@ import { setupOnUninit, } from "../../base/lib/inits" import { DropGenerator } from "../../base/lib/util/Drop" +import { + getOwnServiceInterface, + ServiceInterfaceFilled, +} from "../../base/lib/util/getServiceInterface" +import { getOwnServiceInterfaces } from "../../base/lib/util/getServiceInterfaces" export const OSVersion = testTypeVersion("0.4.0-alpha.16") @@ -170,20 +175,10 @@ export class StartSdk { packageIds?: DependencyId[], ) => Promise>, serviceInterface: { - getOwn: (effects: E, id: ServiceInterfaceId) => - getServiceInterface(effects, { - id, - }), - get: ( - effects: E, - opts: { id: ServiceInterfaceId; packageId: PackageId }, - ) => getServiceInterface(effects, opts), - getAllOwn: (effects: E) => - getServiceInterfaces(effects, {}), - getAll: ( - effects: E, - opts: { packageId: PackageId }, - ) => getServiceInterfaces(effects, opts), + getOwn: getOwnServiceInterface, + get: getServiceInterface, + getAllOwn: getOwnServiceInterfaces, + getAll: getServiceInterfaces, }, getContainerIp: ( effects: T.Effects, diff --git a/sdk/package/lib/util/fileHelper.ts b/sdk/package/lib/util/fileHelper.ts index 1bf2766b9..ad4d9970a 100644 --- a/sdk/package/lib/util/fileHelper.ts +++ b/sdk/package/lib/util/fileHelper.ts @@ -4,38 +4,11 @@ import * as TOML from "@iarna/toml" import * as INI from "ini" import * as T from "../../../base/lib/types" import * as fs from "node:fs/promises" -import { asError } from "../../../base/lib/util" +import { asError, deepEqual } from "../../../base/lib/util" import { DropGenerator, DropPromise } from "../../../base/lib/util/Drop" const previousPath = /(.+?)\/([^/]*)$/ -const deepEq = (left: unknown, right: unknown) => { - if (left === right) return true - if (Array.isArray(left) && Array.isArray(right)) { - if (left.length === right.length) { - for (const idx in left) { - if (!deepEq(left[idx], right[idx])) return false - } - return true - } - } else if ( - typeof left === "object" && - typeof right === "object" && - left && - right - ) { - const keys = new Set([ - ...(Object.keys(left) as (keyof typeof left)[]), - ...(Object.keys(right) as (keyof typeof right)[]), - ]) - for (let key of keys) { - if (!deepEq(left[key], right[key])) return false - } - return true - } - return false -} - const exists = (path: string) => fs.access(path).then( () => true, @@ -374,7 +347,7 @@ export class FileHelper { eq?: (left: any, right: any) => boolean, ): ReadType { map = map ?? ((a: A) => a) - eq = eq ?? deepEq + eq = eq ?? deepEqual return { once: () => this.readOnce(map), const: (effects: T.Effects) => this.readConst(effects, map, eq), diff --git a/web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts b/web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts index fcbd350f4..c91cc25e1 100644 --- a/web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/form/controls/select.component.ts @@ -48,7 +48,7 @@ import { HintPipe } from '../pipes/hint.pipe' } @if (!mobile) { - @for (item of items; track $index) { + @for (item of items; track item) {