mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
wip start-tunneld
This commit is contained in:
8
Makefile
8
Makefile
@@ -102,10 +102,13 @@ test-container-runtime: container-runtime/node_modules/.package-lock.json $(shel
|
|||||||
cd container-runtime && npm test
|
cd container-runtime && npm test
|
||||||
|
|
||||||
cli:
|
cli:
|
||||||
cd core && ./install-cli.sh
|
./core/install-cli.sh
|
||||||
|
|
||||||
registry:
|
registry:
|
||||||
cd core && ./build-registrybox.sh
|
./core/build-registrybox.sh
|
||||||
|
|
||||||
|
tunnel:
|
||||||
|
./core/build-tunnelbox.sh
|
||||||
|
|
||||||
deb: results/$(BASENAME).deb
|
deb: results/$(BASENAME).deb
|
||||||
|
|
||||||
@@ -129,7 +132,6 @@ install: $(ALL_TARGETS)
|
|||||||
$(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,$(DESTDIR)/usr/bin/startbox)
|
$(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,$(DESTDIR)/usr/bin/startbox)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-sdk)
|
|
||||||
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
||||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi
|
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi
|
||||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ Description=StartOS Container Runtime Failure Handler
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/start-cli rebuild
|
ExecStart=/usr/bin/start-container rebuild
|
||||||
@@ -39,8 +39,8 @@ sudo cp container-runtime.service tmp/combined/lib/systemd/system/container-runt
|
|||||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
||||||
sudo cp container-runtime-failure.service tmp/combined/lib/systemd/system/container-runtime-failure.service
|
sudo cp container-runtime-failure.service tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime-failure.service
|
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||||
sudo cp ../core/target/$ARCH-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-cli
|
sudo cp ../core/target/$ARCH-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-container
|
||||||
sudo chown 0:0 tmp/combined/usr/bin/start-cli
|
sudo chown 0:0 tmp/combined/usr/bin/start-container
|
||||||
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id
|
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id
|
||||||
cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash
|
cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash
|
||||||
sudo truncate -s 0 tmp/combined/etc/machine-id
|
sudo truncate -s 0 tmp/combined/etc/machine-id
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl"
|
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl"
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -30,7 +30,7 @@ alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl"
|
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl"
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/registrybox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/registrybox | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v
|
|||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,daemon,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl"
|
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl"
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/startbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/startbox | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
36
core/build-tunnelbox.sh
Executable file
36
core/build-tunnelbox.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
set -ea
|
||||||
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
if [ -z "$ARCH" ]; then
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ARCH" = "arm64" ]; then
|
||||||
|
ARCH="aarch64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
USE_TTY=
|
||||||
|
if tty -s; then
|
||||||
|
USE_TTY="-it"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
|
RUSTFLAGS=""
|
||||||
|
|
||||||
|
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||||
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
||||||
|
|
||||||
|
echo "FEATURES=\"$FEATURES\""
|
||||||
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
|
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$ARCH-unknown-linux-musl"
|
||||||
|
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/tunnelbox | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
@@ -16,4 +16,4 @@ if [ "$PLATFORM" = "arm64" ]; then
|
|||||||
PLATFORM="aarch64"
|
PLATFORM="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cargo install --path=./startos --no-default-features --features=cli,docker,registry --bin start-cli --locked
|
cargo install --path=./startos --no-default-features --features=cli,docker --bin start-cli --locked
|
||||||
|
|||||||
@@ -37,16 +37,24 @@ path = "src/main.rs"
|
|||||||
name = "registrybox"
|
name = "registrybox"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tunnelbox"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = []
|
cli = ["cli-startd", "cli-registry", "cli-tunnel"]
|
||||||
container-runtime = ["procfs", "pty-process"]
|
cli-container = ["procfs", "pty-process"]
|
||||||
daemon = ["mail-send"]
|
cli-registry = []
|
||||||
registry = []
|
cli-startd = []
|
||||||
default = ["cli", "daemon", "registry", "container-runtime"]
|
cli-tunnel = []
|
||||||
|
default = ["cli", "startd", "registry", "cli-container", "tunnel"]
|
||||||
dev = []
|
dev = []
|
||||||
unstable = ["console-subscriber", "tokio/tracing"]
|
|
||||||
docker = []
|
docker = []
|
||||||
|
registry = []
|
||||||
|
startd = ["mail-send"]
|
||||||
test = []
|
test = []
|
||||||
|
tunnel = []
|
||||||
|
unstable = ["console-subscriber", "tokio/tracing"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = { version = "0.7.5", features = ["ctr"] }
|
aes = { version = "0.7.5", features = ["ctr"] }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use imbl_value::InternedString;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use torut::onion::TorSecretKeyV3;
|
use torut::onion::TorSecretKeyV3;
|
||||||
@@ -28,7 +29,7 @@ pub struct AccountInfo {
|
|||||||
pub root_ca_key: PKey<Private>,
|
pub root_ca_key: PKey<Private>,
|
||||||
pub root_ca_cert: X509,
|
pub root_ca_cert: X509,
|
||||||
pub ssh_key: ssh_key::PrivateKey,
|
pub ssh_key: ssh_key::PrivateKey,
|
||||||
pub compat_s9pk_key: ed25519_dalek::SigningKey,
|
pub developer_key: ed25519_dalek::SigningKey,
|
||||||
}
|
}
|
||||||
impl AccountInfo {
|
impl AccountInfo {
|
||||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||||
@@ -40,7 +41,7 @@ impl AccountInfo {
|
|||||||
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
));
|
));
|
||||||
let compat_s9pk_key =
|
let developer_key =
|
||||||
ed25519_dalek::SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
ed25519_dalek::SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_id,
|
server_id,
|
||||||
@@ -50,7 +51,7 @@ impl AccountInfo {
|
|||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
compat_s9pk_key,
|
developer_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ impl AccountInfo {
|
|||||||
let root_ca_key = cert_store.as_root_key().de()?.0;
|
let root_ca_key = cert_store.as_root_key().de()?.0;
|
||||||
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
||||||
let ssh_key = db.as_private().as_ssh_privkey().de()?.0;
|
let ssh_key = db.as_private().as_ssh_privkey().de()?.0;
|
||||||
let compat_s9pk_key = db.as_private().as_compat_s9pk_key().de()?.0;
|
let compat_s9pk_key = db.as_private().as_developer_key().de()?.0;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_id,
|
server_id,
|
||||||
@@ -84,7 +85,7 @@ impl AccountInfo {
|
|||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
compat_s9pk_key,
|
developer_key: compat_s9pk_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +112,8 @@ impl AccountInfo {
|
|||||||
.as_ssh_privkey_mut()
|
.as_ssh_privkey_mut()
|
||||||
.ser(Pem::new_ref(&self.ssh_key))?;
|
.ser(Pem::new_ref(&self.ssh_key))?;
|
||||||
db.as_private_mut()
|
db.as_private_mut()
|
||||||
.as_compat_s9pk_key_mut()
|
.as_developer_key_mut()
|
||||||
.ser(Pem::new_ref(&self.compat_s9pk_key))?;
|
.ser(Pem::new_ref(&self.developer_key))?;
|
||||||
let key_store = db.as_private_mut().as_key_store_mut();
|
let key_store = db.as_private_mut().as_key_store_mut();
|
||||||
for tor_key in &self.tor_keys {
|
for tor_key in &self.tor_keys {
|
||||||
key_store.as_onion_mut().insert_key(tor_key)?;
|
key_store.as_onion_mut().insert_key(tor_key)?;
|
||||||
@@ -131,4 +132,17 @@ impl AccountInfo {
|
|||||||
self.password = hash_password(password)?;
|
self.password = hash_password(password)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hostnames(&self) -> impl IntoIterator<Item = InternedString> + Send + '_ {
|
||||||
|
[
|
||||||
|
self.hostname.no_dot_host_name(),
|
||||||
|
self.hostname.local_domain_name(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
self.tor_keys
|
||||||
|
.iter()
|
||||||
|
.map(|k| InternedString::from_display(&k.public().get_onion_address())),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,15 @@ use imbl_value::{json, InternedString};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn_async, CallRemote, Context, HandlerArgs, HandlerExt, ParentHandler};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::DatabaseModel;
|
|
||||||
use crate::middleware::auth::{
|
use crate::middleware::auth::{
|
||||||
AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes,
|
AsLogoutSessionId, AuthContext, HasLoggedOutSessions, HashSessionToken, LoginRes,
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::crypto::EncryptedWire;
|
use crate::util::crypto::EncryptedWire;
|
||||||
@@ -112,31 +111,34 @@ impl std::str::FromStr for PasswordType {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn auth<C: Context>() -> ParentHandler<C> {
|
pub fn auth<C: Context, AC: AuthContext>() -> ParentHandler<C>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<AC>,
|
||||||
|
{
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"login",
|
"login",
|
||||||
from_fn_async(login_impl)
|
from_fn_async(login_impl::<AC>)
|
||||||
.with_metadata("login", Value::Bool(true))
|
.with_metadata("login", Value::Bool(true))
|
||||||
.no_cli(),
|
.no_cli(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"login",
|
"login",
|
||||||
from_fn_async(cli_login)
|
from_fn_async(cli_login::<AC>)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Log in to StartOS server"),
|
.with_about("Log in a new auth session"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"logout",
|
"logout",
|
||||||
from_fn_async(logout)
|
from_fn_async(logout::<AC>)
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Log out of StartOS server")
|
.with_about("Log out of current auth session")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"session",
|
"session",
|
||||||
session::<C>().with_about("List or kill StartOS sessions"),
|
session::<C, AC>().with_about("List or kill auth sessions"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"reset-password",
|
"reset-password",
|
||||||
@@ -146,7 +148,7 @@ pub fn auth<C: Context>() -> ParentHandler<C> {
|
|||||||
"reset-password",
|
"reset-password",
|
||||||
from_fn_async(cli_reset_password)
|
from_fn_async(cli_reset_password)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Reset StartOS password"),
|
.with_about("Reset password"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"get-pubkey",
|
"get-pubkey",
|
||||||
@@ -172,17 +174,20 @@ fn gen_pwd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn cli_login(
|
async fn cli_login<C: AuthContext>(
|
||||||
HandlerArgs {
|
HandlerArgs {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
parent_method,
|
parent_method,
|
||||||
method,
|
method,
|
||||||
..
|
..
|
||||||
}: HandlerArgs<CliContext>,
|
}: HandlerArgs<CliContext>,
|
||||||
) -> Result<(), RpcError> {
|
) -> Result<(), RpcError>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<C>,
|
||||||
|
{
|
||||||
let password = rpassword::prompt_password("Password: ")?;
|
let password = rpassword::prompt_password("Password: ")?;
|
||||||
|
|
||||||
ctx.call_remote::<RpcContext>(
|
ctx.call_remote::<C>(
|
||||||
&parent_method.into_iter().chain(method).join("."),
|
&parent_method.into_iter().chain(method).join("."),
|
||||||
json!({
|
json!({
|
||||||
"password": password,
|
"password": password,
|
||||||
@@ -210,17 +215,11 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_password_against_db(db: &DatabaseModel, password: &str) -> Result<(), Error> {
|
|
||||||
let pw_hash = db.as_private().as_password().de()?;
|
|
||||||
check_password(&pw_hash, password)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct LoginParams {
|
pub struct LoginParams {
|
||||||
password: Option<PasswordType>,
|
password: String,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_userAgent")] // from Auth middleware
|
#[serde(rename = "__auth_userAgent")] // from Auth middleware
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
@@ -229,20 +228,18 @@ pub struct LoginParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn login_impl(
|
pub async fn login_impl<C: AuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
LoginParams {
|
LoginParams {
|
||||||
password,
|
password,
|
||||||
user_agent,
|
user_agent,
|
||||||
ephemeral,
|
ephemeral,
|
||||||
}: LoginParams,
|
}: LoginParams,
|
||||||
) -> Result<LoginRes, Error> {
|
) -> Result<LoginRes, Error> {
|
||||||
let password = password.unwrap_or_default().decrypt(&ctx)?;
|
|
||||||
|
|
||||||
let tok = if ephemeral {
|
let tok = if ephemeral {
|
||||||
check_password_against_db(&ctx.db.peek().await, &password)?;
|
C::check_password(&ctx.db().peek().await, &password)?;
|
||||||
let hash_token = HashSessionToken::new();
|
let hash_token = HashSessionToken::new();
|
||||||
ctx.ephemeral_sessions.mutate(|s| {
|
ctx.ephemeral_sessions().mutate(|s| {
|
||||||
s.0.insert(
|
s.0.insert(
|
||||||
hash_token.hashed().clone(),
|
hash_token.hashed().clone(),
|
||||||
Session {
|
Session {
|
||||||
@@ -254,11 +251,11 @@ pub async fn login_impl(
|
|||||||
});
|
});
|
||||||
Ok(hash_token.to_login_res())
|
Ok(hash_token.to_login_res())
|
||||||
} else {
|
} else {
|
||||||
ctx.db
|
ctx.db()
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
check_password_against_db(db, &password)?;
|
C::check_password(db, &password)?;
|
||||||
let hash_token = HashSessionToken::new();
|
let hash_token = HashSessionToken::new();
|
||||||
db.as_private_mut().as_sessions_mut().insert(
|
C::access_sessions(db).insert(
|
||||||
hash_token.hashed(),
|
hash_token.hashed(),
|
||||||
&Session {
|
&Session {
|
||||||
logged_in: Utc::now(),
|
logged_in: Utc::now(),
|
||||||
@@ -273,12 +270,7 @@ pub async fn login_impl(
|
|||||||
.result
|
.result
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if tokio::fs::metadata("/media/startos/config/overlay/etc/shadow")
|
ctx.post_login_hook(&password).await?;
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
write_shadow(&password).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tok)
|
Ok(tok)
|
||||||
}
|
}
|
||||||
@@ -292,8 +284,8 @@ pub struct LogoutParams {
|
|||||||
session: InternedString,
|
session: InternedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn logout(
|
pub async fn logout<C: AuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
LogoutParams { session }: LogoutParams,
|
LogoutParams { session }: LogoutParams,
|
||||||
) -> Result<Option<HasLoggedOutSessions>, Error> {
|
) -> Result<Option<HasLoggedOutSessions>, Error> {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
@@ -321,22 +313,25 @@ pub struct SessionList {
|
|||||||
sessions: Sessions,
|
sessions: Sessions,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session<C: Context>() -> ParentHandler<C> {
|
pub fn session<C: Context, AC: AuthContext>() -> ParentHandler<C>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<AC>,
|
||||||
|
{
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"list",
|
"list",
|
||||||
from_fn_async(list)
|
from_fn_async(list::<AC>)
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
||||||
.with_about("Display all server sessions")
|
.with_about("Display all auth sessions")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"kill",
|
"kill",
|
||||||
from_fn_async(kill)
|
from_fn_async(kill::<AC>)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Terminate existing server session(s)")
|
.with_about("Terminate existing auth session(s)")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -385,12 +380,12 @@ pub struct ListParams {
|
|||||||
|
|
||||||
// #[command(display(display_sessions))]
|
// #[command(display(display_sessions))]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn list(
|
pub async fn list<C: AuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
ListParams { session, .. }: ListParams,
|
ListParams { session, .. }: ListParams,
|
||||||
) -> Result<SessionList, Error> {
|
) -> Result<SessionList, Error> {
|
||||||
let mut sessions = ctx.db.peek().await.into_private().into_sessions().de()?;
|
let mut sessions = C::access_sessions(&mut ctx.db().peek().await).de()?;
|
||||||
ctx.ephemeral_sessions.peek(|s| {
|
ctx.ephemeral_sessions().peek(|s| {
|
||||||
sessions
|
sessions
|
||||||
.0
|
.0
|
||||||
.extend(s.0.iter().map(|(k, v)| (k.clone(), v.clone())))
|
.extend(s.0.iter().map(|(k, v)| (k.clone(), v.clone())))
|
||||||
@@ -424,7 +419,7 @@ pub struct KillParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn kill(ctx: RpcContext, KillParams { ids }: KillParams) -> Result<(), Error> {
|
pub async fn kill<C: AuthContext>(ctx: C, KillParams { ids }: KillParams) -> Result<(), Error> {
|
||||||
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?;
|
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use super::target::{BackupTargetId, PackageBackupInfo};
|
use super::target::{BackupTargetId, PackageBackupInfo};
|
||||||
use super::PackageBackupReport;
|
use super::PackageBackupReport;
|
||||||
use crate::auth::check_password_against_db;
|
|
||||||
use crate::backup::os::OsBackup;
|
use crate::backup::os::OsBackup;
|
||||||
use crate::backup::{BackupReport, ServerBackupReport};
|
use crate::backup::{BackupReport, ServerBackupReport};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
@@ -24,6 +23,7 @@ use crate::db::model::{Database, DatabaseModel};
|
|||||||
use crate::disk::mount::backup::BackupMountGuard;
|
use crate::disk::mount::backup::BackupMountGuard;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
|
use crate::middleware::auth::AuthContext;
|
||||||
use crate::notifications::{notify, NotificationLevel};
|
use crate::notifications::{notify, NotificationLevel};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::dir_copy;
|
use crate::util::io::dir_copy;
|
||||||
@@ -170,7 +170,7 @@ pub async fn backup_all(
|
|||||||
let ((fs, package_ids, server_id), status_guard) = (
|
let ((fs, package_ids, server_id), status_guard) = (
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
check_password_against_db(db, &password)?;
|
RpcContext::check_password(db, &password)?;
|
||||||
let fs = target_id.load(db)?;
|
let fs = target_id.load(db)?;
|
||||||
let package_ids = if let Some(ids) = package_ids {
|
let package_ids = if let Some(ids) = package_ids {
|
||||||
ids.into_iter().collect()
|
ids.into_iter().collect()
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ impl OsBackupV0 {
|
|||||||
ssh_key::Algorithm::Ed25519,
|
ssh_key::Algorithm::Ed25519,
|
||||||
)?,
|
)?,
|
||||||
tor_keys: vec![TorSecretKeyV3::from(self.tor_key.0)],
|
tor_keys: vec![TorSecretKeyV3::from(self.tor_key.0)],
|
||||||
compat_s9pk_key: ed25519_dalek::SigningKey::generate(
|
developer_key: ed25519_dalek::SigningKey::generate(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -117,7 +117,7 @@ impl OsBackupV1 {
|
|||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
||||||
tor_keys: vec![TorSecretKeyV3::from(ed25519_expand_key(&self.net_key.0))],
|
tor_keys: vec![TorSecretKeyV3::from(ed25519_expand_key(&self.net_key.0))],
|
||||||
compat_s9pk_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
developer_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ impl OsBackupV2 {
|
|||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: self.ssh_key.0,
|
ssh_key: self.ssh_key.0,
|
||||||
tor_keys: self.tor_keys,
|
tor_keys: self.tor_keys,
|
||||||
compat_s9pk_key: self.compat_s9pk_key.0,
|
developer_key: self.compat_s9pk_key.0,
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
}
|
}
|
||||||
@@ -162,7 +162,7 @@ impl OsBackupV2 {
|
|||||||
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
||||||
ssh_key: Pem(backup.account.ssh_key.clone()),
|
ssh_key: Pem(backup.account.ssh_key.clone()),
|
||||||
tor_keys: backup.account.tor_keys.clone(),
|
tor_keys: backup.account.tor_keys.clone(),
|
||||||
compat_s9pk_key: Pem(backup.account.compat_s9pk_key.clone()),
|
compat_s9pk_key: Pem(backup.account.developer_key.clone()),
|
||||||
ui: backup.ui.clone(),
|
ui: backup.ui.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,41 +2,64 @@ use std::collections::VecDeque;
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(feature = "container-runtime")]
|
#[cfg(feature = "cli-container")]
|
||||||
pub mod container_cli;
|
pub mod container_cli;
|
||||||
pub mod deprecated;
|
pub mod deprecated;
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(feature = "registry")]
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub mod start_cli;
|
pub mod start_cli;
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "startd")]
|
||||||
pub mod start_init;
|
pub mod start_init;
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "startd")]
|
||||||
pub mod startd;
|
pub mod startd;
|
||||||
|
#[cfg(feature = "tunnel")]
|
||||||
|
pub mod tunnel;
|
||||||
|
|
||||||
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
||||||
match name {
|
match name {
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "startd")]
|
||||||
"start-cli" => Some(start_cli::main),
|
|
||||||
#[cfg(feature = "container-runtime")]
|
|
||||||
"start-cli" => Some(container_cli::main),
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
"startd" => Some(startd::main),
|
"startd" => Some(startd::main),
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(feature = "startd")]
|
||||||
"registry" => Some(registry::main),
|
|
||||||
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
|
||||||
"embassy-sdk" => Some(|_| deprecated::renamed("embassy-sdk", "start-sdk")),
|
|
||||||
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
||||||
|
#[cfg(feature = "startd")]
|
||||||
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
||||||
|
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
"start-cli" => Some(start_cli::main),
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
"embassy-sdk" => Some(|_| deprecated::removed("embassy-sdk")),
|
||||||
|
|
||||||
|
#[cfg(feature = "cli-container")]
|
||||||
|
"start-container" => Some(container_cli::main),
|
||||||
|
|
||||||
|
#[cfg(feature = "registry")]
|
||||||
|
"start-registryd" => Some(registry::main),
|
||||||
|
#[cfg(feature = "cli-registry")]
|
||||||
|
"start-registry" => Some(registry::cli),
|
||||||
|
|
||||||
|
#[cfg(feature = "tunnel")]
|
||||||
|
"start-tunneld" => Some(tunnel::main),
|
||||||
|
#[cfg(feature = "cli-tunnel")]
|
||||||
|
"start-tunnel" => Some(tunnel::cli),
|
||||||
|
|
||||||
"contents" => Some(|_| {
|
"contents" => Some(|_| {
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "startd")]
|
||||||
println!("start-cli");
|
|
||||||
#[cfg(feature = "container-runtime")]
|
|
||||||
println!("start-cli (container)");
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
println!("startd");
|
println!("startd");
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
println!("start-cli");
|
||||||
|
#[cfg(feature = "cli-container")]
|
||||||
|
println!("start-container");
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(feature = "registry")]
|
||||||
println!("registry");
|
println!("start-registryd");
|
||||||
|
#[cfg(feature = "cli-registry")]
|
||||||
|
println!("start-registry");
|
||||||
|
#[cfg(feature = "tunnel")]
|
||||||
|
println!("start-tunneld");
|
||||||
|
#[cfg(feature = "cli-tunnel")]
|
||||||
|
println!("start-tunnel");
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ use std::ffi::OsString;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use rpc_toolkit::CliApp;
|
||||||
use tokio::signal::unix::signal;
|
use tokio::signal::unix::signal;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::context::config::ClientConfig;
|
||||||
|
use crate::context::CliContext;
|
||||||
use crate::net::web_server::{Acceptor, WebServer};
|
use crate::net::web_server::{Acceptor, WebServer};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::context::{RegistryConfig, RegistryContext};
|
use crate::registry::context::{RegistryConfig, RegistryContext};
|
||||||
@@ -85,3 +88,30 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
if let Err(e) = CliApp::new(
|
||||||
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
|
crate::registry::registry_api(),
|
||||||
|
)
|
||||||
|
.run(args)
|
||||||
|
{
|
||||||
|
match e.data {
|
||||||
|
Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||||
|
Some(serde_json::Value::Object(o)) => {
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("details") {
|
||||||
|
eprintln!("{}: {}", e.message, s);
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("debug") {
|
||||||
|
tracing::debug!("{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||||
|
None => eprintln!("{}", e.message),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(e.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
117
core/startos/src/bins/tunnel.rs
Normal file
117
core/startos/src/bins/tunnel.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use futures::FutureExt;
|
||||||
|
use rpc_toolkit::CliApp;
|
||||||
|
use tokio::signal::unix::signal;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::context::config::ClientConfig;
|
||||||
|
use crate::context::CliContext;
|
||||||
|
use crate::net::web_server::{Acceptor, WebServer};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
||||||
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||||
|
let server = async {
|
||||||
|
let ctx = TunnelContext::init(config).await?;
|
||||||
|
let mut server = WebServer::new(Acceptor::bind([ctx.listen]).await?);
|
||||||
|
server.serve_tunnel(ctx.clone());
|
||||||
|
|
||||||
|
let mut shutdown_recv = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
|
let sig_handler_ctx = ctx;
|
||||||
|
let sig_handler = tokio::spawn(async move {
|
||||||
|
use tokio::signal::unix::SignalKind;
|
||||||
|
futures::future::select_all(
|
||||||
|
[
|
||||||
|
SignalKind::interrupt(),
|
||||||
|
SignalKind::quit(),
|
||||||
|
SignalKind::terminate(),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
async move {
|
||||||
|
signal(*s)
|
||||||
|
.unwrap_or_else(|_| panic!("register {:?} handler", s))
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
sig_handler_ctx
|
||||||
|
.shutdown
|
||||||
|
.send(())
|
||||||
|
.map_err(|_| ())
|
||||||
|
.expect("send shutdown signal");
|
||||||
|
});
|
||||||
|
|
||||||
|
shutdown_recv
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.with_kind(crate::ErrorKind::Unknown)?;
|
||||||
|
|
||||||
|
sig_handler.abort();
|
||||||
|
|
||||||
|
Ok::<_, Error>(server)
|
||||||
|
}
|
||||||
|
.await?;
|
||||||
|
server.shutdown().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
let config = TunnelConfig::parse_from(args).load().unwrap();
|
||||||
|
|
||||||
|
let res = {
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("failed to initialize runtime");
|
||||||
|
rt.block_on(inner_main(&config))
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{}", e.source);
|
||||||
|
tracing::debug!("{:?}", e.source);
|
||||||
|
drop(e.source);
|
||||||
|
std::process::exit(e.kind as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
if let Err(e) = CliApp::new(
|
||||||
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
|
crate::tunnel::tunnel_api(),
|
||||||
|
)
|
||||||
|
.run(args)
|
||||||
|
{
|
||||||
|
match e.data {
|
||||||
|
Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||||
|
Some(serde_json::Value::Object(o)) => {
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("details") {
|
||||||
|
eprintln!("{}: {}", e.message, s);
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("debug") {
|
||||||
|
tracing::debug!("{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||||
|
None => eprintln!("{}", e.message),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(e.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use cookie::{Cookie, Expiration, SameSite};
|
||||||
use cookie_store::{CookieStore, RawCookie};
|
use cookie_store::{CookieStore, RawCookie};
|
||||||
|
use imbl_value::InternedString;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use reqwest::Proxy;
|
use reqwest::Proxy;
|
||||||
@@ -19,9 +22,12 @@ use tracing::instrument;
|
|||||||
use super::setup::CURRENT_SECRET;
|
use super::setup::CURRENT_SECRET;
|
||||||
use crate::context::config::{local_config_path, ClientConfig};
|
use crate::context::config::{local_config_path, ClientConfig};
|
||||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
use crate::developer::{default_developer_key_path, OS_DEVELOPER_KEY_PATH};
|
||||||
|
use crate::middleware::auth::AuthContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
|
use crate::tunnel::context::TunnelContext;
|
||||||
|
use crate::tunnel::TUNNEL_DEFAULT_PORT;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CliContextSeed {
|
pub struct CliContextSeed {
|
||||||
@@ -29,6 +35,10 @@ pub struct CliContextSeed {
|
|||||||
pub base_url: Url,
|
pub base_url: Url,
|
||||||
pub rpc_url: Url,
|
pub rpc_url: Url,
|
||||||
pub registry_url: Option<Url>,
|
pub registry_url: Option<Url>,
|
||||||
|
pub registry_hostname: Option<InternedString>,
|
||||||
|
pub registry_listen: Option<SocketAddr>,
|
||||||
|
pub tunnel_addr: Option<SocketAddr>,
|
||||||
|
pub tunnel_listen: Option<SocketAddr>,
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
pub cookie_store: Arc<CookieStoreMutex>,
|
pub cookie_store: Arc<CookieStoreMutex>,
|
||||||
pub cookie_path: PathBuf,
|
pub cookie_path: PathBuf,
|
||||||
@@ -55,9 +65,8 @@ impl Drop for CliContextSeed {
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut store = self.cookie_store.lock().unwrap();
|
let store = self.cookie_store.lock().unwrap();
|
||||||
store.remove("localhost", "", "local");
|
cookie_store::serde::json::save(&store, &mut *writer).unwrap();
|
||||||
store.save_json(&mut *writer).unwrap();
|
|
||||||
writer.sync_all().unwrap();
|
writer.sync_all().unwrap();
|
||||||
std::fs::rename(tmp, &self.cookie_path).unwrap();
|
std::fs::rename(tmp, &self.cookie_path).unwrap();
|
||||||
}
|
}
|
||||||
@@ -85,26 +94,14 @@ impl CliContext {
|
|||||||
.unwrap_or(Path::new("/"))
|
.unwrap_or(Path::new("/"))
|
||||||
.join(".cookies.json")
|
.join(".cookies.json")
|
||||||
});
|
});
|
||||||
let cookie_store = Arc::new(CookieStoreMutex::new({
|
let cookie_store = Arc::new(CookieStoreMutex::new(if cookie_path.exists() {
|
||||||
let mut store = if cookie_path.exists() {
|
cookie_store::serde::json::load(BufReader::new(
|
||||||
CookieStore::load_json(BufReader::new(
|
File::open(&cookie_path)
|
||||||
File::open(&cookie_path)
|
.with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?,
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?,
|
))
|
||||||
))
|
.unwrap_or_default()
|
||||||
.map_err(|e| eyre!("{}", e))
|
} else {
|
||||||
.with_kind(crate::ErrorKind::Deserialization)?
|
CookieStore::default()
|
||||||
} else {
|
|
||||||
CookieStore::default()
|
|
||||||
};
|
|
||||||
if let Ok(local) = std::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH) {
|
|
||||||
store
|
|
||||||
.insert_raw(
|
|
||||||
&RawCookie::new("local", local),
|
|
||||||
&"http://localhost".parse()?,
|
|
||||||
)
|
|
||||||
.with_kind(crate::ErrorKind::Network)?;
|
|
||||||
}
|
|
||||||
store
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Ok(CliContext(Arc::new(CliContextSeed {
|
Ok(CliContext(Arc::new(CliContextSeed {
|
||||||
@@ -129,6 +126,10 @@ impl CliContext {
|
|||||||
Ok::<_, Error>(registry)
|
Ok::<_, Error>(registry)
|
||||||
})
|
})
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
|
registry_hostname: config.registry_hostname,
|
||||||
|
registry_listen: config.registry_listen,
|
||||||
|
tunnel_addr: config.tunnel,
|
||||||
|
tunnel_listen: config.tunnel_listen,
|
||||||
client: {
|
client: {
|
||||||
let mut builder = Client::builder().cookie_provider(cookie_store.clone());
|
let mut builder = Client::builder().cookie_provider(cookie_store.clone());
|
||||||
if let Some(proxy) = config.proxy {
|
if let Some(proxy) = config.proxy {
|
||||||
@@ -139,14 +140,9 @@ impl CliContext {
|
|||||||
},
|
},
|
||||||
cookie_store,
|
cookie_store,
|
||||||
cookie_path,
|
cookie_path,
|
||||||
developer_key_path: config.developer_key_path.unwrap_or_else(|| {
|
developer_key_path: config
|
||||||
local_config_path()
|
.developer_key_path
|
||||||
.as_deref()
|
.unwrap_or_else(default_developer_key_path),
|
||||||
.unwrap_or_else(|| Path::new(super::config::CONFIG_PATH))
|
|
||||||
.parent()
|
|
||||||
.unwrap_or(Path::new("/"))
|
|
||||||
.join("developer.key.pem")
|
|
||||||
}),
|
|
||||||
developer_key: OnceCell::new(),
|
developer_key: OnceCell::new(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@@ -155,20 +151,26 @@ impl CliContext {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn developer_key(&self) -> Result<&ed25519_dalek::SigningKey, Error> {
|
pub fn developer_key(&self) -> Result<&ed25519_dalek::SigningKey, Error> {
|
||||||
self.developer_key.get_or_try_init(|| {
|
self.developer_key.get_or_try_init(|| {
|
||||||
if !self.developer_key_path.exists() {
|
for path in [Path::new(OS_DEVELOPER_KEY_PATH), &self.developer_key_path] {
|
||||||
return Err(Error::new(eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."), crate::ErrorKind::Uninitialized));
|
if !path.exists() {
|
||||||
}
|
continue;
|
||||||
let pair = <ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
}
|
||||||
&std::fs::read_to_string(&self.developer_key_path)?,
|
let pair = <ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
||||||
)
|
&std::fs::read_to_string(&self.developer_key_path)?,
|
||||||
.with_kind(crate::ErrorKind::Pem)?;
|
|
||||||
let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("pkcs8 key is of incorrect length"),
|
|
||||||
ErrorKind::OpenSsl,
|
|
||||||
)
|
)
|
||||||
})?;
|
.with_kind(crate::ErrorKind::Pem)?;
|
||||||
Ok(secret.into())
|
let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("pkcs8 key is of incorrect length"),
|
||||||
|
ErrorKind::OpenSsl,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
return Ok(secret.into())
|
||||||
|
}
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."),
|
||||||
|
crate::ErrorKind::Uninitialized
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,27 +278,90 @@ impl Context for CliContext {
|
|||||||
}
|
}
|
||||||
impl CallRemote<RpcContext> for CliContext {
|
impl CallRemote<RpcContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
if let Ok(local) = std::fs::read_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH) {
|
||||||
|
self.cookie_store
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert_raw(
|
||||||
|
&Cookie::build(("local", local))
|
||||||
|
.domain("localhost")
|
||||||
|
.expires(Expiration::Session)
|
||||||
|
.same_site(SameSite::Strict)
|
||||||
|
.build(),
|
||||||
|
&"http://localhost".parse()?,
|
||||||
|
)
|
||||||
|
.with_kind(crate::ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<DiagnosticContext> for CliContext {
|
impl CallRemote<DiagnosticContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
if let Ok(local) = std::fs::read_to_string(TunnelContext::LOCAL_AUTH_COOKIE_PATH) {
|
||||||
|
self.cookie_store
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert_raw(
|
||||||
|
&Cookie::build(("local", local))
|
||||||
|
.domain("localhost")
|
||||||
|
.expires(Expiration::Session)
|
||||||
|
.same_site(SameSite::Strict)
|
||||||
|
.build(),
|
||||||
|
&"http://localhost".parse()?,
|
||||||
|
)
|
||||||
|
.with_kind(crate::ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InitContext> for CliContext {
|
impl CallRemote<InitContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<SetupContext> for CliContext {
|
impl CallRemote<SetupContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InstallContext> for CliContext {
|
impl CallRemote<InstallContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::net::SocketAddr;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use imbl_value::InternedString;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -55,7 +56,6 @@ pub trait ContextConfig: DeserializeOwned + Default {
|
|||||||
#[derive(Debug, Default, Deserialize, Serialize, Parser)]
|
#[derive(Debug, Default, Deserialize, Serialize, Parser)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
#[command(name = "start-cli")]
|
|
||||||
#[command(version = crate::version::Current::default().semver().to_string())]
|
#[command(version = crate::version::Current::default().semver().to_string())]
|
||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
#[arg(short = 'c', long)]
|
#[arg(short = 'c', long)]
|
||||||
@@ -64,6 +64,14 @@ pub struct ClientConfig {
|
|||||||
pub host: Option<Url>,
|
pub host: Option<Url>,
|
||||||
#[arg(short = 'r', long)]
|
#[arg(short = 'r', long)]
|
||||||
pub registry: Option<Url>,
|
pub registry: Option<Url>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub registry_hostname: Option<InternedString>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub registry_listen: Option<SocketAddr>,
|
||||||
|
#[arg(short = 't', long)]
|
||||||
|
pub tunnel: Option<SocketAddr>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub tunnel_listen: Option<SocketAddr>,
|
||||||
#[arg(short = 'p', long)]
|
#[arg(short = 'p', long)]
|
||||||
pub proxy: Option<Url>,
|
pub proxy: Option<Url>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
@@ -78,6 +86,8 @@ impl ContextConfig for ClientConfig {
|
|||||||
fn merge_with(&mut self, other: Self) {
|
fn merge_with(&mut self, other: Self) {
|
||||||
self.host = self.host.take().or(other.host);
|
self.host = self.host.take().or(other.host);
|
||||||
self.registry = self.registry.take().or(other.registry);
|
self.registry = self.registry.take().or(other.registry);
|
||||||
|
self.registry_hostname = self.registry_hostname.take().or(other.registry_hostname);
|
||||||
|
self.tunnel = self.tunnel.take().or(other.tunnel);
|
||||||
self.proxy = self.proxy.take().or(other.proxy);
|
self.proxy = self.proxy.take().or(other.proxy);
|
||||||
self.cookie_path = self.cookie_path.take().or(other.cookie_path);
|
self.cookie_path = self.cookie_path.take().or(other.cookie_path);
|
||||||
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
||||||
@@ -113,6 +123,8 @@ pub struct ServerConfig {
|
|||||||
pub disable_encryption: Option<bool>,
|
pub disable_encryption: Option<bool>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub multi_arch_s9pks: Option<bool>,
|
pub multi_arch_s9pks: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub developer_key_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
impl ContextConfig for ServerConfig {
|
impl ContextConfig for ServerConfig {
|
||||||
fn next(&mut self) -> Option<PathBuf> {
|
fn next(&mut self) -> Option<PathBuf> {
|
||||||
@@ -129,6 +141,7 @@ impl ContextConfig for ServerConfig {
|
|||||||
.or(other.revision_cache_size);
|
.or(other.revision_cache_size);
|
||||||
self.disable_encryption = self.disable_encryption.take().or(other.disable_encryption);
|
self.disable_encryption = self.disable_encryption.take().or(other.disable_encryption);
|
||||||
self.multi_arch_s9pks = self.multi_arch_s9pks.take().or(other.multi_arch_s9pks);
|
self.multi_arch_s9pks = self.multi_arch_s9pks.take().or(other.multi_arch_s9pks);
|
||||||
|
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::net::forward::AvailablePorts;
|
|||||||
use crate::net::keys::KeyStore;
|
use crate::net::keys::KeyStore;
|
||||||
use crate::notifications::Notifications;
|
use crate::notifications::Notifications;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
use crate::ssh::SshKeys;
|
use crate::ssh::SshKeys;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
@@ -33,6 +34,9 @@ impl Database {
|
|||||||
private: Private {
|
private: Private {
|
||||||
key_store: KeyStore::new(account)?,
|
key_store: KeyStore::new(account)?,
|
||||||
password: account.password.clone(),
|
password: account.password.clone(),
|
||||||
|
auth_pubkeys: [AnyVerifyingKey::Ed25519((&account.developer_key).into())]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
ssh_privkey: Pem(account.ssh_key.clone()),
|
ssh_privkey: Pem(account.ssh_key.clone()),
|
||||||
ssh_pubkeys: SshKeys::new(),
|
ssh_pubkeys: SshKeys::new(),
|
||||||
available_ports: AvailablePorts::new(),
|
available_ports: AvailablePorts::new(),
|
||||||
@@ -40,7 +44,7 @@ impl Database {
|
|||||||
notifications: Notifications::new(),
|
notifications: Notifications::new(),
|
||||||
cifs: CifsTargets::new(),
|
cifs: CifsTargets::new(),
|
||||||
package_stores: BTreeMap::new(),
|
package_stores: BTreeMap::new(),
|
||||||
compat_s9pk_key: Pem(account.compat_s9pk_key.clone()),
|
developer_key: Pem(account.developer_key.clone()),
|
||||||
}, // TODO
|
}, // TODO
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, HashSet};
|
||||||
|
|
||||||
use models::PackageId;
|
use models::PackageId;
|
||||||
use patch_db::{HasModel, Value};
|
use patch_db::{HasModel, Value};
|
||||||
@@ -10,6 +10,7 @@ use crate::net::forward::AvailablePorts;
|
|||||||
use crate::net::keys::KeyStore;
|
use crate::net::keys::KeyStore;
|
||||||
use crate::notifications::Notifications;
|
use crate::notifications::Notifications;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
use crate::ssh::SshKeys;
|
use crate::ssh::SshKeys;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
@@ -19,8 +20,9 @@ use crate::util::serde::Pem;
|
|||||||
pub struct Private {
|
pub struct Private {
|
||||||
pub key_store: KeyStore,
|
pub key_store: KeyStore,
|
||||||
pub password: String, // argon2 hash
|
pub password: String, // argon2 hash
|
||||||
#[serde(default = "generate_compat_key")]
|
pub auth_pubkeys: HashSet<AnyVerifyingKey>,
|
||||||
pub compat_s9pk_key: Pem<ed25519_dalek::SigningKey>,
|
#[serde(default = "generate_developer_key")]
|
||||||
|
pub developer_key: Pem<ed25519_dalek::SigningKey>,
|
||||||
pub ssh_privkey: Pem<ssh_key::PrivateKey>,
|
pub ssh_privkey: Pem<ssh_key::PrivateKey>,
|
||||||
pub ssh_pubkeys: SshKeys,
|
pub ssh_pubkeys: SshKeys,
|
||||||
pub available_ports: AvailablePorts,
|
pub available_ports: AvailablePorts,
|
||||||
@@ -31,7 +33,7 @@ pub struct Private {
|
|||||||
pub package_stores: BTreeMap<PackageId, Value>,
|
pub package_stores: BTreeMap<PackageId, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_compat_key() -> Pem<ed25519_dalek::SigningKey> {
|
pub fn generate_developer_key() -> Pem<ed25519_dalek::SigningKey> {
|
||||||
Pem(ed25519_dalek::SigningKey::generate(
|
Pem(ed25519_dalek::SigningKey::generate(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,40 +1,57 @@
|
|||||||
use std::fs::File;
|
use std::path::{Path, PathBuf};
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use ed25519::pkcs8::EncodePrivateKey;
|
use ed25519::pkcs8::EncodePrivateKey;
|
||||||
use ed25519::PublicKeyBytes;
|
use ed25519::PublicKeyBytes;
|
||||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::context::config::local_config_path;
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::util::io::create_file_mod;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
|
pub const OS_DEVELOPER_KEY_PATH: &str = "/run/startos/developer.key.pem";
|
||||||
|
|
||||||
|
pub fn default_developer_key_path() -> PathBuf {
|
||||||
|
local_config_path()
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_else(|| Path::new(crate::context::config::CONFIG_PATH))
|
||||||
|
.parent()
|
||||||
|
.unwrap_or(Path::new("/"))
|
||||||
|
.join("developer.key.pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_developer_key(
|
||||||
|
secret: &ed25519_dalek::SigningKey,
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let keypair_bytes = ed25519::KeypairBytes {
|
||||||
|
secret_key: secret.to_bytes(),
|
||||||
|
public_key: Some(PublicKeyBytes(VerifyingKey::from(secret).to_bytes())),
|
||||||
|
};
|
||||||
|
let mut file = create_file_mod(path, 0o046).await?;
|
||||||
|
file.write_all(
|
||||||
|
keypair_bytes
|
||||||
|
.to_pkcs8_pem(base64ct::LineEnding::default())
|
||||||
|
.with_kind(crate::ErrorKind::Pem)?
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
file.sync_all().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn init(ctx: CliContext) -> Result<(), Error> {
|
pub async fn init(ctx: CliContext) -> Result<(), Error> {
|
||||||
if !ctx.developer_key_path.exists() {
|
if tokio::fs::metadata(OS_DEVELOPER_KEY_PATH).await.is_ok() {
|
||||||
let parent = ctx.developer_key_path.parent().unwrap_or(Path::new("/"));
|
println!("Developer key already exists at {}", OS_DEVELOPER_KEY_PATH);
|
||||||
if !parent.exists() {
|
} else if tokio::fs::metadata(&ctx.developer_key_path).await.is_err() {
|
||||||
std::fs::create_dir_all(parent)
|
|
||||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, parent.display().to_string()))?;
|
|
||||||
}
|
|
||||||
tracing::info!("Generating new developer key...");
|
tracing::info!("Generating new developer key...");
|
||||||
let secret = SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
let secret = SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
||||||
tracing::info!("Writing key to {}", ctx.developer_key_path.display());
|
tracing::info!("Writing key to {}", ctx.developer_key_path.display());
|
||||||
let keypair_bytes = ed25519::KeypairBytes {
|
write_developer_key(&secret, &ctx.developer_key_path).await?;
|
||||||
secret_key: secret.to_bytes(),
|
|
||||||
public_key: Some(PublicKeyBytes(VerifyingKey::from(&secret).to_bytes())),
|
|
||||||
};
|
|
||||||
let mut dev_key_file = File::create(&ctx.developer_key_path)
|
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, ctx.developer_key_path.display()))?;
|
|
||||||
dev_key_file.write_all(
|
|
||||||
keypair_bytes
|
|
||||||
.to_pkcs8_pem(base64ct::LineEnding::default())
|
|
||||||
.with_kind(crate::ErrorKind::Pem)?
|
|
||||||
.as_bytes(),
|
|
||||||
)?;
|
|
||||||
dev_key_file.sync_all()?;
|
|
||||||
println!(
|
println!(
|
||||||
"New developer key generated at {}",
|
"New developer key generated at {}",
|
||||||
ctx.developer_key_path.display()
|
ctx.developer_key_path.display()
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
use std::fs::Permissions;
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use axum::extract::ws::{self};
|
use axum::extract::ws;
|
||||||
use color_eyre::eyre::eyre;
|
|
||||||
use const_format::formatcp;
|
use const_format::formatcp;
|
||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::ResultExt;
|
use models::ResultExt;
|
||||||
use rand::random;
|
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -21,12 +17,12 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::context::{CliContext, InitContext};
|
use crate::context::{CliContext, InitContext, RpcContext};
|
||||||
use crate::db::model::public::ServerStatus;
|
use crate::db::model::public::ServerStatus;
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
use crate::disk::mount::util::unmount;
|
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
use crate::middleware::auth::AuthContext;
|
||||||
use crate::net::net_controller::{NetController, NetService};
|
use crate::net::net_controller::{NetController, NetService};
|
||||||
use crate::net::utils::find_wifi_iface;
|
use crate::net::utils::find_wifi_iface;
|
||||||
use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter};
|
use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter};
|
||||||
@@ -38,7 +34,7 @@ use crate::rpc_continuations::{Guid, RpcContinuation};
|
|||||||
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
||||||
use crate::ssh::SSH_DIR;
|
use crate::ssh::SSH_DIR;
|
||||||
use crate::system::{get_mem_info, sync_kiosk};
|
use crate::system::{get_mem_info, sync_kiosk};
|
||||||
use crate::util::io::{create_file, open_file, IOHook};
|
use crate::util::io::{open_file, IOHook};
|
||||||
use crate::util::lshw::lshw;
|
use crate::util::lshw::lshw;
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::util::{cpupower, Invoke};
|
use crate::util::{cpupower, Invoke};
|
||||||
@@ -167,28 +163,7 @@ pub async fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
local_auth.start();
|
local_auth.start();
|
||||||
tokio::fs::create_dir_all("/run/startos")
|
RpcContext::init_auth_cookie().await?;
|
||||||
.await
|
|
||||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, "mkdir -p /run/startos"))?;
|
|
||||||
if tokio::fs::metadata(LOCAL_AUTH_COOKIE_PATH).await.is_err() {
|
|
||||||
tokio::fs::write(
|
|
||||||
LOCAL_AUTH_COOKIE_PATH,
|
|
||||||
base64::encode(random::<[u8; 32]>()).as_bytes(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.with_ctx(|_| {
|
|
||||||
(
|
|
||||||
crate::ErrorKind::Filesystem,
|
|
||||||
format!("write {}", LOCAL_AUTH_COOKIE_PATH),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
tokio::fs::set_permissions(LOCAL_AUTH_COOKIE_PATH, Permissions::from_mode(0o046)).await?;
|
|
||||||
Command::new("chown")
|
|
||||||
.arg("root:startos")
|
|
||||||
.arg(LOCAL_AUTH_COOKIE_PATH)
|
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
local_auth.complete();
|
local_auth.complete();
|
||||||
|
|
||||||
load_database.start();
|
load_database.start();
|
||||||
@@ -199,6 +174,16 @@ pub async fn init(
|
|||||||
load_database.complete();
|
load_database.complete();
|
||||||
|
|
||||||
load_ssh_keys.start();
|
load_ssh_keys.start();
|
||||||
|
crate::developer::write_developer_key(
|
||||||
|
&peek.as_private().as_developer_key().de()?.0,
|
||||||
|
OS_DEVELOPER_KEY_PATH,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Command::new("chown")
|
||||||
|
.arg("root:startos")
|
||||||
|
.arg(OS_DEVELOPER_KEY_PATH)
|
||||||
|
.invoke(ErrorKind::Filesystem)
|
||||||
|
.await?;
|
||||||
crate::ssh::sync_keys(
|
crate::ssh::sync_keys(
|
||||||
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
||||||
&peek.as_private().as_ssh_privkey().de()?,
|
&peek.as_private().as_ssh_privkey().de()?,
|
||||||
@@ -206,6 +191,13 @@ pub async fn init(
|
|||||||
SSH_DIR,
|
SSH_DIR,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
crate::ssh::sync_keys(
|
||||||
|
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
||||||
|
&peek.as_private().as_ssh_privkey().de()?,
|
||||||
|
&Default::default(),
|
||||||
|
"/root/.ssh",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
load_ssh_keys.complete();
|
load_ssh_keys.complete();
|
||||||
|
|
||||||
let account = AccountInfo::load(&peek)?;
|
let account = AccountInfo::load(&peek)?;
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ pub async fn sideload(
|
|||||||
.await;
|
.await;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = async {
|
if let Err(e) = async {
|
||||||
let key = ctx.db.peek().await.into_private().into_compat_s9pk_key();
|
let key = ctx.db.peek().await.into_private().into_developer_key();
|
||||||
|
|
||||||
ctx.services
|
ctx.services
|
||||||
.install(
|
.install(
|
||||||
|
|||||||
@@ -60,10 +60,12 @@ pub mod s9pk;
|
|||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod shutdown;
|
pub mod shutdown;
|
||||||
|
pub mod sign;
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
pub mod ssh;
|
pub mod ssh;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
|
pub mod tunnel;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
pub mod upload;
|
pub mod upload;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
@@ -152,9 +154,8 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"auth",
|
"auth",
|
||||||
auth::auth::<C>().with_about(
|
auth::auth::<C, RpcContext>()
|
||||||
"Commands related to Authentication i.e. login, logout, reset-password",
|
.with_about("Commands related to Authentication i.e. login, logout"),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"db",
|
"db",
|
||||||
@@ -582,7 +583,7 @@ pub fn expanded_api() -> ParentHandler<CliContext> {
|
|||||||
main_api()
|
main_api()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"init",
|
"init",
|
||||||
from_fn_blocking(developer::init)
|
from_fn_async(developer::init)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Create developer key if it doesn't exist"),
|
.with_about("Create developer key if it doesn't exist"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::future::Future;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
|
use base64::Engine;
|
||||||
use basic_cookies::Cookie;
|
use basic_cookies::Cookie;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
@@ -13,17 +15,131 @@ use digest::Digest;
|
|||||||
use helpers::const_true;
|
use helpers::const_true;
|
||||||
use http::header::{COOKIE, USER_AGENT};
|
use http::header::{COOKIE, USER_AGENT};
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::{json, InternedString};
|
||||||
|
use rand::random;
|
||||||
use rpc_toolkit::yajrc::INTERNAL_ERROR;
|
use rpc_toolkit::yajrc::INTERNAL_ERROR;
|
||||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::process::Command;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::auth::{check_password, write_shadow, Sessions};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
|
use crate::db::model::Database;
|
||||||
|
use crate::middleware::signature::{SignatureAuth, SignatureAuthContext};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::rpc_continuations::OpenAuthedContinuations;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
|
use crate::util::io::{create_file_mod, read_file_to_string};
|
||||||
|
use crate::util::iter::TransposeResultIterExt;
|
||||||
|
use crate::util::serde::BASE64;
|
||||||
|
use crate::util::sync::SyncMutex;
|
||||||
|
use crate::util::Invoke;
|
||||||
|
|
||||||
pub const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
pub trait AuthContext: SignatureAuthContext {
|
||||||
|
const LOCAL_AUTH_COOKIE_PATH: &str;
|
||||||
|
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str;
|
||||||
|
fn init_auth_cookie() -> impl Future<Output = Result<(), Error>> + Send {
|
||||||
|
async {
|
||||||
|
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o046).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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn ephemeral_sessions(&self) -> &SyncMutex<Sessions>;
|
||||||
|
fn open_authed_continuations(&self) -> &OpenAuthedContinuations<Option<InternedString>>;
|
||||||
|
fn access_sessions(db: &mut Model<Self::Database>) -> &mut Model<Sessions>;
|
||||||
|
fn check_password(db: &Model<Self::Database>, password: &str) -> Result<(), Error>;
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn post_login_hook(&self, password: &str) -> impl Future<Output = Result<(), Error>> + Send {
|
||||||
|
async { Ok(()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatureAuthContext for RpcContext {
|
||||||
|
type Database = Database;
|
||||||
|
type AdditionalMetadata = ();
|
||||||
|
type CheckPubkeyRes = ();
|
||||||
|
fn db(&self) -> &TypedPatchDb<Self::Database> {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
async fn sig_context(
|
||||||
|
&self,
|
||||||
|
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
||||||
|
let peek = self.db.peek().await;
|
||||||
|
self.account
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.hostnames()
|
||||||
|
.into_iter()
|
||||||
|
.map(Ok)
|
||||||
|
.chain(
|
||||||
|
peek.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_host()
|
||||||
|
.as_domains()
|
||||||
|
.keys()
|
||||||
|
.map(|k| k.into_iter())
|
||||||
|
.transpose(),
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
fn check_pubkey(
|
||||||
|
db: &Model<Self::Database>,
|
||||||
|
pubkey: Option<&AnyVerifyingKey>,
|
||||||
|
_: Self::AdditionalMetadata,
|
||||||
|
) -> Result<Self::CheckPubkeyRes, Error> {
|
||||||
|
if let Some(pubkey) = pubkey {
|
||||||
|
if db.as_private().as_auth_pubkeys().de()?.contains(pubkey) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Developer Key is not authorized"),
|
||||||
|
ErrorKind::IncorrectPassword,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
async fn post_auth_hook(&self, _: Self::CheckPubkeyRes, _: &RpcRequest) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AuthContext for RpcContext {
|
||||||
|
const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
||||||
|
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos";
|
||||||
|
fn ephemeral_sessions(&self) -> &SyncMutex<Sessions> {
|
||||||
|
&self.ephemeral_sessions
|
||||||
|
}
|
||||||
|
fn open_authed_continuations(&self) -> &OpenAuthedContinuations<Option<InternedString>> {
|
||||||
|
&self.open_authed_continuations
|
||||||
|
}
|
||||||
|
fn access_sessions(db: &mut Model<Self::Database>) -> &mut Model<Sessions> {
|
||||||
|
db.as_private_mut().as_sessions_mut()
|
||||||
|
}
|
||||||
|
fn check_password(db: &Model<Self::Database>, password: &str) -> Result<(), Error> {
|
||||||
|
check_password(&db.as_private().as_password().de()?, password)
|
||||||
|
}
|
||||||
|
async fn post_login_hook(&self, password: &str) -> Result<(), Error> {
|
||||||
|
if tokio::fs::metadata("/media/startos/config/overlay/etc/shadow")
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
write_shadow(&password).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -40,25 +156,25 @@ pub trait AsLogoutSessionId {
|
|||||||
pub struct HasLoggedOutSessions(());
|
pub struct HasLoggedOutSessions(());
|
||||||
|
|
||||||
impl HasLoggedOutSessions {
|
impl HasLoggedOutSessions {
|
||||||
pub async fn new(
|
pub async fn new<C: AuthContext>(
|
||||||
sessions: impl IntoIterator<Item = impl AsLogoutSessionId>,
|
sessions: impl IntoIterator<Item = impl AsLogoutSessionId>,
|
||||||
ctx: &RpcContext,
|
ctx: &C,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let to_log_out: BTreeSet<_> = sessions
|
let to_log_out: BTreeSet<_> = sessions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.as_logout_session_id())
|
.map(|s| s.as_logout_session_id())
|
||||||
.collect();
|
.collect();
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
ctx.open_authed_continuations.kill(&Some(sid.clone()))
|
ctx.open_authed_continuations().kill(&Some(sid.clone()))
|
||||||
}
|
}
|
||||||
ctx.ephemeral_sessions.mutate(|s| {
|
ctx.ephemeral_sessions().mutate(|s| {
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
s.0.remove(sid);
|
s.0.remove(sid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ctx.db
|
ctx.db()
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let sessions = db.as_private_mut().as_sessions_mut();
|
let sessions = C::access_sessions(db);
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
sessions.remove(sid)?;
|
sessions.remove(sid)?;
|
||||||
}
|
}
|
||||||
@@ -82,9 +198,9 @@ enum SessionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HasValidSession {
|
impl HasValidSession {
|
||||||
pub async fn from_header(
|
pub async fn from_header<C: AuthContext>(
|
||||||
header: Option<&HeaderValue>,
|
header: Option<&HeaderValue>,
|
||||||
ctx: &RpcContext,
|
ctx: &C,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
if let Some(cookie_header) = header {
|
if let Some(cookie_header) = header {
|
||||||
let cookies = Cookie::parse(
|
let cookies = Cookie::parse(
|
||||||
@@ -94,7 +210,7 @@ impl HasValidSession {
|
|||||||
)
|
)
|
||||||
.with_kind(crate::ErrorKind::Authorization)?;
|
.with_kind(crate::ErrorKind::Authorization)?;
|
||||||
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") {
|
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") {
|
||||||
if let Ok(s) = Self::from_local(cookie).await {
|
if let Ok(s) = Self::from_local::<C>(cookie).await {
|
||||||
return Ok(s);
|
return Ok(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,12 +227,12 @@ impl HasValidSession {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_session(
|
pub async fn from_session<C: AuthContext>(
|
||||||
session_token: HashSessionToken,
|
session_token: HashSessionToken,
|
||||||
ctx: &RpcContext,
|
ctx: &C,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let session_hash = session_token.hashed();
|
let session_hash = session_token.hashed();
|
||||||
if !ctx.ephemeral_sessions.mutate(|s| {
|
if !ctx.ephemeral_sessions().mutate(|s| {
|
||||||
if let Some(session) = s.0.get_mut(session_hash) {
|
if let Some(session) = s.0.get_mut(session_hash) {
|
||||||
session.last_active = Utc::now();
|
session.last_active = Utc::now();
|
||||||
true
|
true
|
||||||
@@ -124,10 +240,9 @@ impl HasValidSession {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
ctx.db
|
ctx.db()
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_private_mut()
|
C::access_sessions(db)
|
||||||
.as_sessions_mut()
|
|
||||||
.as_idx_mut(session_hash)
|
.as_idx_mut(session_hash)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||||
@@ -143,8 +258,8 @@ impl HasValidSession {
|
|||||||
Ok(Self(SessionType::Session(session_token)))
|
Ok(Self(SessionType::Session(session_token)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_local(local: &Cookie<'_>) -> Result<Self, Error> {
|
pub async fn from_local<C: AuthContext>(local: &Cookie<'_>) -> Result<Self, Error> {
|
||||||
let token = tokio::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH).await?;
|
let token = read_file_to_string(C::LOCAL_AUTH_COOKIE_PATH).await?;
|
||||||
if local.get_value() == &*token {
|
if local.get_value() == &*token {
|
||||||
Ok(Self(SessionType::Local))
|
Ok(Self(SessionType::Local))
|
||||||
} else {
|
} else {
|
||||||
@@ -258,6 +373,8 @@ pub struct Metadata {
|
|||||||
login: bool,
|
login: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
get_session: bool,
|
get_session: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
get_signer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -267,6 +384,7 @@ pub struct Auth {
|
|||||||
is_login: bool,
|
is_login: bool,
|
||||||
set_cookie: Option<HeaderValue>,
|
set_cookie: Option<HeaderValue>,
|
||||||
user_agent: Option<HeaderValue>,
|
user_agent: Option<HeaderValue>,
|
||||||
|
signature_auth: SignatureAuth,
|
||||||
}
|
}
|
||||||
impl Auth {
|
impl Auth {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -276,62 +394,73 @@ impl Auth {
|
|||||||
is_login: false,
|
is_login: false,
|
||||||
set_cookie: None,
|
set_cookie: None,
|
||||||
user_agent: None,
|
user_agent: None,
|
||||||
|
signature_auth: SignatureAuth::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Middleware<RpcContext> for Auth {
|
impl<C: AuthContext> Middleware<C> for Auth {
|
||||||
type Metadata = Metadata;
|
type Metadata = Metadata;
|
||||||
async fn process_http_request(
|
async fn process_http_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &RpcContext,
|
context: &C,
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
) -> Result<(), Response> {
|
) -> Result<(), Response> {
|
||||||
self.cookie = request.headers_mut().remove(COOKIE);
|
self.cookie = request.headers_mut().remove(COOKIE);
|
||||||
self.user_agent = request.headers_mut().remove(USER_AGENT);
|
self.user_agent = request.headers_mut().remove(USER_AGENT);
|
||||||
|
self.signature_auth
|
||||||
|
.process_http_request(context, request)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn process_rpc_request(
|
async fn process_rpc_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &RpcContext,
|
context: &C,
|
||||||
metadata: Self::Metadata,
|
metadata: Self::Metadata,
|
||||||
request: &mut RpcRequest,
|
request: &mut RpcRequest,
|
||||||
) -> Result<(), RpcResponse> {
|
) -> Result<(), RpcResponse> {
|
||||||
if metadata.login {
|
async {
|
||||||
self.is_login = true;
|
if metadata.login {
|
||||||
let guard = self.rate_limiter.lock().await;
|
self.is_login = true;
|
||||||
if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 {
|
let guard = self.rate_limiter.lock().await;
|
||||||
return Err(RpcResponse {
|
if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 {
|
||||||
id: request.id.take(),
|
return Err(Error::new(
|
||||||
result: Err(Error::new(
|
|
||||||
eyre!("Please limit login attempts to 3 per 20 seconds."),
|
eyre!("Please limit login attempts to 3 per 20 seconds."),
|
||||||
crate::ErrorKind::RateLimited,
|
crate::ErrorKind::RateLimited,
|
||||||
)
|
));
|
||||||
.into()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
match HasValidSession::from_header(self.cookie.as_ref(), &context).await {
|
|
||||||
Err(e) => {
|
|
||||||
return Err(RpcResponse {
|
|
||||||
id: request.id.take(),
|
|
||||||
result: Err(e.into()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Ok(HasValidSession(SessionType::Session(s))) if metadata.get_session => {
|
if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) {
|
||||||
request.params["__auth_session"] =
|
request.params["__auth_userAgent"] =
|
||||||
Value::String(Arc::new(s.hashed().deref().to_owned()));
|
Value::String(Arc::new(user_agent.to_owned()))
|
||||||
// TODO: will this panic?
|
// 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()));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
.await
|
||||||
|
.map_err(|e| RpcResponse::from_result(Err(e)))
|
||||||
}
|
}
|
||||||
async fn process_rpc_response(&mut self, _: &RpcContext, response: &mut RpcResponse) {
|
async fn process_rpc_response(&mut self, _: &C, response: &mut RpcResponse) {
|
||||||
if self.is_login {
|
if self.is_login {
|
||||||
let mut guard = self.rate_limiter.lock().await;
|
let mut guard = self.rate_limiter.lock().await;
|
||||||
if guard.1.elapsed() < Duration::from_secs(20) {
|
if guard.1.elapsed() < Duration::from_secs(20) {
|
||||||
@@ -349,7 +478,7 @@ impl Middleware<RpcContext> for Auth {
|
|||||||
let login_res = from_value::<LoginRes>(res.clone())?;
|
let login_res = from_value::<LoginRes>(res.clone())?;
|
||||||
self.set_cookie = Some(
|
self.set_cookie = Some(
|
||||||
HeaderValue::from_str(&format!(
|
HeaderValue::from_str(&format!(
|
||||||
"session={}; Path=/; SameSite=Lax; Expires=Fri, 31 Dec 9999 23:59:59 GMT;",
|
"session={}; Path=/; SameSite=Strict; Expires=Fri, 31 Dec 9999 23:59:59 GMT;",
|
||||||
login_res.session
|
login_res.session
|
||||||
))
|
))
|
||||||
.with_kind(crate::ErrorKind::Network)?,
|
.with_kind(crate::ErrorKind::Network)?,
|
||||||
@@ -361,7 +490,7 @@ impl Middleware<RpcContext> for Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn process_http_response(&mut self, _: &RpcContext, response: &mut Response) {
|
async fn process_http_response(&mut self, _: &C, response: &mut Response) {
|
||||||
if let Some(set_cookie) = self.set_cookie.take() {
|
if let Some(set_cookie) = self.set_cookie.take() {
|
||||||
response.headers_mut().insert("set-cookie", set_cookie);
|
response.headers_mut().insert("set-cookie", set_cookie);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod cors;
|
pub mod cors;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod signature;
|
||||||
|
|||||||
@@ -1,45 +1,62 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::future::Future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use axum::response::Response;
|
|
||||||
use chrono::Utc;
|
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
use rpc_toolkit::{Context, Middleware, RpcRequest, RpcResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::de::DeserializeOwned;
|
||||||
use tokio::io::AsyncWriteExt;
|
use serde::Deserialize;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use ts_rs::TS;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::sign::commitment::request::RequestCommitment;
|
||||||
use crate::registry::signer::commitment::request::RequestCommitment;
|
use crate::sign::commitment::Commitment;
|
||||||
use crate::registry::signer::commitment::Commitment;
|
use crate::sign::{AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme};
|
||||||
use crate::registry::signer::sign::{
|
|
||||||
AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme,
|
|
||||||
};
|
|
||||||
use crate::util::serde::Base64;
|
use crate::util::serde::Base64;
|
||||||
|
|
||||||
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Registry-Auth-Sig";
|
pub trait SignatureAuthContext: Context {
|
||||||
|
type Database: HasModel<Model = Model<Self::Database>> + Send + Sync;
|
||||||
|
type AdditionalMetadata: DeserializeOwned + Send;
|
||||||
|
type CheckPubkeyRes: Send;
|
||||||
|
fn db(&self) -> &TypedPatchDb<Self::Database>;
|
||||||
|
fn sig_context(
|
||||||
|
&self,
|
||||||
|
) -> impl Future<Output = impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send>
|
||||||
|
+ Send;
|
||||||
|
fn check_pubkey(
|
||||||
|
db: &Model<Self::Database>,
|
||||||
|
pubkey: Option<&AnyVerifyingKey>,
|
||||||
|
metadata: Self::AdditionalMetadata,
|
||||||
|
) -> Result<Self::CheckPubkeyRes, Error>;
|
||||||
|
fn post_auth_hook(
|
||||||
|
&self,
|
||||||
|
check_pubkey_res: Self::CheckPubkeyRes,
|
||||||
|
request: &RpcRequest,
|
||||||
|
) -> impl Future<Output = Result<(), Error>> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Auth-Sig";
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Metadata {
|
pub struct Metadata<Additional> {
|
||||||
#[serde(default)]
|
#[serde(flatten)]
|
||||||
admin: bool,
|
additional: Additional,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
get_signer: bool,
|
get_signer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Auth {
|
pub struct SignatureAuth {
|
||||||
nonce_cache: Arc<Mutex<BTreeMap<Instant, u64>>>, // for replay protection
|
nonce_cache: Arc<Mutex<BTreeMap<Instant, u64>>>, // for replay protection
|
||||||
signer: Option<Result<AnyVerifyingKey, RpcError>>,
|
signer: Option<Result<AnyVerifyingKey, RpcError>>,
|
||||||
}
|
}
|
||||||
impl Auth {
|
impl SignatureAuth {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
nonce_cache: Arc::new(Mutex::new(BTreeMap::new())),
|
nonce_cache: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
@@ -65,15 +82,6 @@ impl Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
|
||||||
pub struct RegistryAdminLogRecord {
|
|
||||||
pub timestamp: String,
|
|
||||||
pub name: String,
|
|
||||||
#[ts(type = "{ id: string | number | null; method: string; params: any }")]
|
|
||||||
pub request: RpcRequest,
|
|
||||||
pub key: AnyVerifyingKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SignatureHeader {
|
pub struct SignatureHeader {
|
||||||
pub commitment: RequestCommitment,
|
pub commitment: RequestCommitment,
|
||||||
pub signer: AnyVerifyingKey,
|
pub signer: AnyVerifyingKey,
|
||||||
@@ -120,13 +128,13 @@ impl SignatureHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Middleware<RegistryContext> for Auth {
|
impl<C: SignatureAuthContext> Middleware<C> for SignatureAuth {
|
||||||
type Metadata = Metadata;
|
type Metadata = Metadata<C::AdditionalMetadata>;
|
||||||
async fn process_http_request(
|
async fn process_http_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &RegistryContext,
|
context: &C,
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
) -> Result<(), Response> {
|
) -> Result<(), axum::response::Response> {
|
||||||
if request.headers().contains_key(AUTH_SIG_HEADER) {
|
if request.headers().contains_key(AUTH_SIG_HEADER) {
|
||||||
self.signer = Some(
|
self.signer = Some(
|
||||||
async {
|
async {
|
||||||
@@ -138,15 +146,27 @@ impl Middleware<RegistryContext> for Auth {
|
|||||||
request
|
request
|
||||||
.headers()
|
.headers()
|
||||||
.get(AUTH_SIG_HEADER)
|
.get(AUTH_SIG_HEADER)
|
||||||
.or_not_found("missing X-StartOS-Registry-Auth-Sig")
|
.or_not_found(AUTH_SIG_HEADER)
|
||||||
.with_kind(ErrorKind::InvalidRequest)?,
|
.with_kind(ErrorKind::InvalidRequest)?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
signer.scheme().verify_commitment(
|
context.sig_context().await.into_iter().fold(
|
||||||
&signer,
|
Err(Error::new(
|
||||||
&commitment,
|
eyre!("no valid signature context available to verify"),
|
||||||
&ctx.hostname,
|
ErrorKind::Authorization,
|
||||||
&signature,
|
)),
|
||||||
|
|acc, x| {
|
||||||
|
if acc.is_ok() {
|
||||||
|
acc
|
||||||
|
} else {
|
||||||
|
signer.scheme().verify_commitment(
|
||||||
|
&signer,
|
||||||
|
&commitment,
|
||||||
|
x?.as_ref(),
|
||||||
|
&signature,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
@@ -175,48 +195,83 @@ impl Middleware<RegistryContext> for Auth {
|
|||||||
}
|
}
|
||||||
async fn process_rpc_request(
|
async fn process_rpc_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &RegistryContext,
|
context: &C,
|
||||||
metadata: Self::Metadata,
|
metadata: Self::Metadata,
|
||||||
request: &mut RpcRequest,
|
request: &mut RpcRequest,
|
||||||
) -> Result<(), RpcResponse> {
|
) -> Result<(), RpcResponse> {
|
||||||
async move {
|
async {
|
||||||
let signer = self.signer.take().transpose()?;
|
let signer = self.signer.take().transpose()?;
|
||||||
if metadata.get_signer {
|
if metadata.get_signer {
|
||||||
if let Some(signer) = &signer {
|
if let Some(signer) = &signer {
|
||||||
request.params["__auth_signer"] = to_value(signer)?;
|
request.params["__auth_signer"] = to_value(signer)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if metadata.admin {
|
let db = context.db().peek().await;
|
||||||
let signer = signer
|
let res = C::check_pubkey(&db, signer.as_ref(), metadata.additional)?;
|
||||||
.ok_or_else(|| Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))?;
|
context.post_auth_hook(res, request).await?;
|
||||||
let db = ctx.db.peek().await;
|
|
||||||
let (guid, admin) = db.as_index().as_signers().get_signer_info(&signer)?;
|
|
||||||
if db.into_admins().de()?.contains(&guid) {
|
|
||||||
let mut log = tokio::fs::OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(ctx.datadir.join("admin.log"))
|
|
||||||
.await?;
|
|
||||||
log.write_all(
|
|
||||||
(serde_json::to_string(&RegistryAdminLogRecord {
|
|
||||||
timestamp: Utc::now().to_rfc3339(),
|
|
||||||
name: admin.name,
|
|
||||||
request: request.clone(),
|
|
||||||
key: signer,
|
|
||||||
})
|
|
||||||
.with_kind(ErrorKind::Serialization)?
|
|
||||||
+ "\n")
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.await
|
.await
|
||||||
.map_err(|e| RpcResponse::from_result(Err(e)))
|
.map_err(|e: Error| rpc_toolkit::RpcResponse::from_result(Err(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_remote(
|
||||||
|
ctx: &CliContext,
|
||||||
|
url: Url,
|
||||||
|
sig_context: &str,
|
||||||
|
method: &str,
|
||||||
|
params: Value,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
|
use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE};
|
||||||
|
use reqwest::Method;
|
||||||
|
use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest};
|
||||||
|
use rpc_toolkit::RpcResponse;
|
||||||
|
|
||||||
|
let rpc_req = RpcRequest {
|
||||||
|
id: Some(Id::Number(0.into())),
|
||||||
|
method: GenericRpcMethod::<_, _, Value>::new(method),
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
let body = serde_json::to_vec(&rpc_req)?;
|
||||||
|
let mut req = ctx
|
||||||
|
.client
|
||||||
|
.request(Method::POST, url)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.header(ACCEPT, "application/json")
|
||||||
|
.header(CONTENT_LENGTH, body.len());
|
||||||
|
if let Ok(key) = ctx.developer_key() {
|
||||||
|
req = req.header(
|
||||||
|
AUTH_SIG_HEADER,
|
||||||
|
SignatureHeader::sign(&AnySigningKey::Ed25519(key.clone()), &body, sig_context)?
|
||||||
|
.to_header(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let res = req.body(body).send().await?;
|
||||||
|
|
||||||
|
if !res.status().is_success() {
|
||||||
|
let status = res.status();
|
||||||
|
let txt = res.text().await?;
|
||||||
|
let mut res = Err(Error::new(
|
||||||
|
eyre!("{}", status.canonical_reason().unwrap_or(status.as_str())),
|
||||||
|
ErrorKind::Network,
|
||||||
|
));
|
||||||
|
if !txt.is_empty() {
|
||||||
|
res = res.with_ctx(|_| (ErrorKind::Network, txt));
|
||||||
|
}
|
||||||
|
return res.map_err(From::from);
|
||||||
|
}
|
||||||
|
|
||||||
|
match res
|
||||||
|
.headers()
|
||||||
|
.get(CONTENT_TYPE)
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
{
|
||||||
|
Some("application/json") => {
|
||||||
|
serde_json::from_slice::<RpcResponse>(&*res.bytes().await?)
|
||||||
|
.with_kind(ErrorKind::Deserialization)?
|
||||||
|
.result
|
||||||
|
}
|
||||||
|
_ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,8 @@ pub async fn init(
|
|||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_public_mut()
|
db.as_public_mut()
|
||||||
.as_server_info_mut().as_network_mut()
|
.as_server_info_mut()
|
||||||
|
.as_network_mut()
|
||||||
.as_acme_mut()
|
.as_acme_mut()
|
||||||
.insert(&provider, &AcmeSettings { contact })
|
.insert(&provider, &AcmeSettings { contact })
|
||||||
})
|
})
|
||||||
@@ -279,7 +280,8 @@ pub async fn remove(
|
|||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_public_mut()
|
db.as_public_mut()
|
||||||
.as_server_info_mut().as_network_mut()
|
.as_server_info_mut()
|
||||||
|
.as_network_mut()
|
||||||
.as_acme_mut()
|
.as_acme_mut()
|
||||||
.remove(&provider)
|
.remove(&provider)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use crate::middleware::auth::{Auth, HasValidSession};
|
|||||||
use crate::middleware::cors::Cors;
|
use crate::middleware::cors::Cors;
|
||||||
use crate::middleware::db::SyncDb;
|
use crate::middleware::db::SyncDb;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuations};
|
use crate::rpc_continuations::{Guid, RpcContinuations};
|
||||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
@@ -55,10 +55,10 @@ const INTERNAL_SERVER_ERROR: &[u8] = b"Internal Server Error";
|
|||||||
|
|
||||||
const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"];
|
const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"];
|
||||||
|
|
||||||
#[cfg(all(feature = "daemon", not(feature = "test")))]
|
#[cfg(all(feature = "startd", not(feature = "test")))]
|
||||||
const EMBEDDED_UIS: Dir<'_> =
|
const EMBEDDED_UIS: Dir<'_> =
|
||||||
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static");
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static");
|
||||||
#[cfg(not(all(feature = "daemon", not(feature = "test"))))]
|
#[cfg(not(all(feature = "startd", not(feature = "test"))))]
|
||||||
const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]);
|
const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use ts_rs::TS;
|
|||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::signer::sign::AnyVerifyingKey;
|
use crate::sign::AnyVerifyingKey;
|
||||||
use crate::registry::signer::{ContactInfo, SignerInfo};
|
use crate::registry::signer::{ContactInfo, SignerInfo};
|
||||||
use crate::registry::RegistryDatabase;
|
use crate::registry::RegistryDatabase;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ use url::Url;
|
|||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::PhaseProgressTrackerHandle;
|
use crate::progress::PhaseProgressTrackerHandle;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::registry::signer::commitment::{Commitment, Digestable};
|
use crate::sign::commitment::{Commitment, Digestable};
|
||||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey};
|
use crate::sign::{AnySignature, AnyVerifyingKey};
|
||||||
use crate::registry::signer::AcceptSigners;
|
use crate::registry::signer::AcceptSigners;
|
||||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||||
use crate::s9pk::merkle_archive::source::{ArchiveSource, Section};
|
use crate::s9pk::merkle_archive::source::{ArchiveSource, Section};
|
||||||
|
|||||||
@@ -3,26 +3,33 @@ use std::ops::Deref;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use patch_db::PatchDb;
|
use patch_db::PatchDb;
|
||||||
use reqwest::{Client, Proxy};
|
use reqwest::{Client, Proxy};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{CallRemote, Context, Empty};
|
use rpc_toolkit::{CallRemote, Context, Empty, RpcRequest};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
use ts_rs::TS;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::context::config::{ContextConfig, CONFIG_PATH};
|
use crate::context::config::{ContextConfig, CONFIG_PATH};
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
|
use crate::middleware::signature::SignatureAuthContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::auth::{SignatureHeader, AUTH_SIG_HEADER};
|
|
||||||
use crate::registry::device_info::{DeviceInfo, DEVICE_INFO_HEADER};
|
use crate::registry::device_info::{DeviceInfo, DEVICE_INFO_HEADER};
|
||||||
use crate::registry::signer::sign::AnySigningKey;
|
use crate::registry::signer::SignerInfo;
|
||||||
use crate::registry::RegistryDatabase;
|
use crate::registry::RegistryDatabase;
|
||||||
use crate::rpc_continuations::RpcContinuations;
|
use crate::rpc_continuations::RpcContinuations;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
|
use crate::util::io::append_file;
|
||||||
|
|
||||||
|
const DEFAULT_REGISTRY_LISTEN: SocketAddr =
|
||||||
|
SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), 5959);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@@ -31,9 +38,9 @@ pub struct RegistryConfig {
|
|||||||
#[arg(short = 'c', long = "config")]
|
#[arg(short = 'c', long = "config")]
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
#[arg(short = 'l', long = "listen")]
|
#[arg(short = 'l', long = "listen")]
|
||||||
pub listen: Option<SocketAddr>,
|
pub registry_listen: Option<SocketAddr>,
|
||||||
#[arg(short = 'h', long = "hostname")]
|
#[arg(short = 'H', long = "hostname")]
|
||||||
pub hostname: Option<InternedString>,
|
pub registry_hostname: Vec<InternedString>,
|
||||||
#[arg(short = 'p', long = "tor-proxy")]
|
#[arg(short = 'p', long = "tor-proxy")]
|
||||||
pub tor_proxy: Option<Url>,
|
pub tor_proxy: Option<Url>,
|
||||||
#[arg(short = 'd', long = "datadir")]
|
#[arg(short = 'd', long = "datadir")]
|
||||||
@@ -45,9 +52,9 @@ impl ContextConfig for RegistryConfig {
|
|||||||
fn next(&mut self) -> Option<PathBuf> {
|
fn next(&mut self) -> Option<PathBuf> {
|
||||||
self.config.take()
|
self.config.take()
|
||||||
}
|
}
|
||||||
fn merge_with(&mut self, other: Self) {
|
fn merge_with(&mut self, mut other: Self) {
|
||||||
self.listen = self.listen.take().or(other.listen);
|
self.registry_listen = self.registry_listen.take().or(other.registry_listen);
|
||||||
self.hostname = self.hostname.take().or(other.hostname);
|
self.registry_hostname.append(&mut other.registry_hostname);
|
||||||
self.tor_proxy = self.tor_proxy.take().or(other.tor_proxy);
|
self.tor_proxy = self.tor_proxy.take().or(other.tor_proxy);
|
||||||
self.datadir = self.datadir.take().or(other.datadir);
|
self.datadir = self.datadir.take().or(other.datadir);
|
||||||
}
|
}
|
||||||
@@ -63,7 +70,7 @@ impl RegistryConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct RegistryContextSeed {
|
pub struct RegistryContextSeed {
|
||||||
pub hostname: InternedString,
|
pub hostnames: Vec<InternedString>,
|
||||||
pub listen: SocketAddr,
|
pub listen: SocketAddr,
|
||||||
pub db: TypedPatchDb<RegistryDatabase>,
|
pub db: TypedPatchDb<RegistryDatabase>,
|
||||||
pub datadir: PathBuf,
|
pub datadir: PathBuf,
|
||||||
@@ -105,20 +112,15 @@ impl RegistryContext {
|
|||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
if config.registry_hostname.is_empty() {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("missing required configuration: registry-hostname"),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
));
|
||||||
|
}
|
||||||
Ok(Self(Arc::new(RegistryContextSeed {
|
Ok(Self(Arc::new(RegistryContextSeed {
|
||||||
hostname: config
|
hostnames: config.registry_hostname.clone(),
|
||||||
.hostname
|
listen: config.registry_listen.unwrap_or(DEFAULT_REGISTRY_LISTEN),
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("missing required configuration: hostname"),
|
|
||||||
ErrorKind::NotFound,
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.clone(),
|
|
||||||
listen: config
|
|
||||||
.listen
|
|
||||||
.unwrap_or(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 5959)),
|
|
||||||
db,
|
db,
|
||||||
datadir,
|
datadir,
|
||||||
rpc_continuations: RpcContinuations::new(),
|
rpc_continuations: RpcContinuations::new(),
|
||||||
@@ -163,64 +165,28 @@ impl CallRemote<RegistryContext> for CliContext {
|
|||||||
params: Value,
|
params: Value,
|
||||||
_: Empty,
|
_: Empty,
|
||||||
) -> Result<Value, RpcError> {
|
) -> Result<Value, RpcError> {
|
||||||
use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE};
|
let url = if let Some(url) = self.registry_url.clone() {
|
||||||
use reqwest::Method;
|
url
|
||||||
use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest};
|
} else if self.registry_hostname.is_some() {
|
||||||
use rpc_toolkit::RpcResponse;
|
format!(
|
||||||
|
"http://{}",
|
||||||
let url = self
|
self.registry_listen.unwrap_or(DEFAULT_REGISTRY_LISTEN)
|
||||||
.registry_url
|
)
|
||||||
.clone()
|
.parse()
|
||||||
.ok_or_else(|| Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest))?;
|
.map_err(Error::from)?
|
||||||
method = method.strip_prefix("registry.").unwrap_or(method);
|
} else {
|
||||||
|
return Err(
|
||||||
let rpc_req = RpcRequest {
|
Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest).into(),
|
||||||
id: Some(Id::Number(0.into())),
|
|
||||||
method: GenericRpcMethod::<_, _, Value>::new(method),
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
let body = serde_json::to_vec(&rpc_req)?;
|
|
||||||
let host = url.host().or_not_found("registry hostname")?.to_string();
|
|
||||||
let mut req = self
|
|
||||||
.client
|
|
||||||
.request(Method::POST, url)
|
|
||||||
.header(CONTENT_TYPE, "application/json")
|
|
||||||
.header(ACCEPT, "application/json")
|
|
||||||
.header(CONTENT_LENGTH, body.len());
|
|
||||||
if let Ok(key) = self.developer_key() {
|
|
||||||
req = req.header(
|
|
||||||
AUTH_SIG_HEADER,
|
|
||||||
SignatureHeader::sign(&AnySigningKey::Ed25519(key.clone()), &body, &host)?
|
|
||||||
.to_header(),
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
let res = req.body(body).send().await?;
|
method = method.strip_prefix("registry.").unwrap_or(method);
|
||||||
|
let sig_context = self
|
||||||
|
.registry_hostname
|
||||||
|
.clone()
|
||||||
|
.or(url.host().as_ref().map(InternedString::from_display))
|
||||||
|
.or_not_found("registry hostname")?;
|
||||||
|
|
||||||
if !res.status().is_success() {
|
crate::middleware::signature::call_remote(self, url, &sig_context, method, params).await
|
||||||
let status = res.status();
|
|
||||||
let txt = res.text().await?;
|
|
||||||
let mut res = Err(Error::new(
|
|
||||||
eyre!("{}", status.canonical_reason().unwrap_or(status.as_str())),
|
|
||||||
ErrorKind::Network,
|
|
||||||
));
|
|
||||||
if !txt.is_empty() {
|
|
||||||
res = res.with_ctx(|_| (ErrorKind::Network, txt));
|
|
||||||
}
|
|
||||||
return res.map_err(From::from);
|
|
||||||
}
|
|
||||||
|
|
||||||
match res
|
|
||||||
.headers()
|
|
||||||
.get(CONTENT_TYPE)
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
{
|
|
||||||
Some("application/json") => {
|
|
||||||
serde_json::from_slice::<RpcResponse>(&*res.bytes().await?)
|
|
||||||
.with_kind(ErrorKind::Deserialization)?
|
|
||||||
.result
|
|
||||||
}
|
|
||||||
_ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,3 +252,72 @@ impl CallRemote<RegistryContext, RegistryUrlParams> for RpcContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct RegistryAuthMetadata {
|
||||||
|
#[serde(default)]
|
||||||
|
admin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
|
pub struct AdminLogRecord {
|
||||||
|
pub timestamp: String,
|
||||||
|
pub name: String,
|
||||||
|
#[ts(type = "{ id: string | number | null; method: string; params: any }")]
|
||||||
|
pub request: RpcRequest,
|
||||||
|
pub key: AnyVerifyingKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatureAuthContext for RegistryContext {
|
||||||
|
type Database = RegistryDatabase;
|
||||||
|
type AdditionalMetadata = RegistryAuthMetadata;
|
||||||
|
type CheckPubkeyRes = Option<(AnyVerifyingKey, SignerInfo)>;
|
||||||
|
fn db(&self) -> &TypedPatchDb<Self::Database> {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
async fn sig_context(
|
||||||
|
&self,
|
||||||
|
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
||||||
|
self.hostnames.iter().map(Ok)
|
||||||
|
}
|
||||||
|
fn check_pubkey(
|
||||||
|
db: &Model<Self::Database>,
|
||||||
|
pubkey: Option<&AnyVerifyingKey>,
|
||||||
|
metadata: Self::AdditionalMetadata,
|
||||||
|
) -> Result<Self::CheckPubkeyRes, Error> {
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn post_auth_hook(
|
||||||
|
&self,
|
||||||
|
check_pubkey_res: Self::CheckPubkeyRes,
|
||||||
|
request: &RpcRequest,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
if let Some((pubkey, admin)) = check_pubkey_res {
|
||||||
|
let mut log = append_file(self.datadir.join("admin.log")).await?;
|
||||||
|
log.write_all(
|
||||||
|
(serde_json::to_string(&AdminLogRecord {
|
||||||
|
timestamp: Utc::now().to_rfc3339(),
|
||||||
|
name: admin.name,
|
||||||
|
request: request.clone(),
|
||||||
|
key: pubkey,
|
||||||
|
})
|
||||||
|
.with_kind(ErrorKind::Serialization)?
|
||||||
|
+ "\n")
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::middleware::cors::Cors;
|
use crate::middleware::cors::Cors;
|
||||||
|
use crate::middleware::signature::SignatureAuth;
|
||||||
use crate::net::static_server::{bad_request, not_found, server_error};
|
use crate::net::static_server::{bad_request, not_found, server_error};
|
||||||
use crate::net::web_server::{Accept, WebServer};
|
use crate::net::web_server::{Accept, WebServer};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::auth::Auth;
|
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::device_info::DeviceInfoMiddleware;
|
use crate::registry::device_info::DeviceInfoMiddleware;
|
||||||
use crate::registry::os::index::OsIndex;
|
use crate::registry::os::index::OsIndex;
|
||||||
@@ -23,7 +23,6 @@ use crate::util::serde::HandlerExtSerde;
|
|||||||
|
|
||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
pub mod auth;
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod device_info;
|
pub mod device_info;
|
||||||
@@ -95,7 +94,7 @@ pub fn registry_router(ctx: RegistryContext) -> Router {
|
|||||||
any(
|
any(
|
||||||
Server::new(move || ready(Ok(ctx.clone())), registry_api())
|
Server::new(move || ready(Ok(ctx.clone())), registry_api())
|
||||||
.middleware(Cors::new())
|
.middleware(Cors::new())
|
||||||
.middleware(Auth::new())
|
.middleware(SignatureAuth::new())
|
||||||
.middleware(DeviceInfoMiddleware::new()),
|
.middleware(DeviceInfoMiddleware::new()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ use crate::registry::asset::RegistryAsset;
|
|||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
use crate::registry::os::SIG_CONTEXT;
|
use crate::registry::os::SIG_CONTEXT;
|
||||||
use crate::registry::signer::commitment::blake3::Blake3Commitment;
|
use crate::sign::commitment::blake3::Blake3Commitment;
|
||||||
use crate::registry::signer::sign::ed25519::Ed25519;
|
use crate::sign::ed25519::Ed25519;
|
||||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
||||||
use crate::s9pk::merkle_archive::hash::VerifyingWriter;
|
use crate::s9pk::merkle_archive::hash::VerifyingWriter;
|
||||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ use crate::registry::asset::RegistryAsset;
|
|||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
use crate::registry::os::SIG_CONTEXT;
|
use crate::registry::os::SIG_CONTEXT;
|
||||||
use crate::registry::signer::commitment::blake3::Blake3Commitment;
|
use crate::sign::commitment::blake3::Blake3Commitment;
|
||||||
use crate::registry::signer::commitment::Commitment;
|
use crate::sign::commitment::Commitment;
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ use crate::registry::asset::RegistryAsset;
|
|||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
use crate::registry::os::SIG_CONTEXT;
|
use crate::registry::os::SIG_CONTEXT;
|
||||||
use crate::registry::signer::commitment::blake3::Blake3Commitment;
|
use crate::sign::commitment::blake3::Blake3Commitment;
|
||||||
use crate::registry::signer::sign::ed25519::Ed25519;
|
use crate::sign::ed25519::Ed25519;
|
||||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use ts_rs::TS;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::asset::RegistryAsset;
|
use crate::registry::asset::RegistryAsset;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::signer::commitment::blake3::Blake3Commitment;
|
use crate::sign::commitment::blake3::Blake3Commitment;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::prelude::*;
|
|||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::device_info::DeviceInfo;
|
use crate::registry::device_info::DeviceInfo;
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
use crate::registry::signer::sign::AnyVerifyingKey;
|
use crate::sign::AnyVerifyingKey;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
||||||
|
|
||||||
pub mod signer;
|
pub mod signer;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ use crate::prelude::*;
|
|||||||
use crate::progress::{FullProgressTracker, ProgressTrackerWriter, ProgressUnits};
|
use crate::progress::{FullProgressTracker, ProgressTrackerWriter, ProgressUnits};
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::package::index::PackageVersionInfo;
|
use crate::registry::package::index::PackageVersionInfo;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::registry::signer::sign::ed25519::Ed25519;
|
use crate::sign::ed25519::Ed25519;
|
||||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
||||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||||
use crate::s9pk::v2::SIG_CONTEXT;
|
use crate::s9pk::v2::SIG_CONTEXT;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ use crate::prelude::*;
|
|||||||
use crate::registry::asset::RegistryAsset;
|
use crate::registry::asset::RegistryAsset;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::device_info::DeviceInfo;
|
use crate::registry::device_info::DeviceInfo;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey};
|
use crate::sign::{AnySignature, AnyVerifyingKey};
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::s9pk::git_hash::GitHash;
|
use crate::s9pk::git_hash::GitHash;
|
||||||
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements};
|
use crate::s9pk::manifest::{Alerts, Description, HardwareRequirements};
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ use ts_rs::TS;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::Digestable;
|
use crate::sign::commitment::Digestable;
|
||||||
use crate::registry::signer::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme};
|
||||||
|
|
||||||
pub mod commitment;
|
|
||||||
pub mod sign;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -7,9 +7,9 @@ use sha2::{Digest, Sha512};
|
|||||||
use tokio::io::AsyncRead;
|
use tokio::io::AsyncRead;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::registry::signer::sign::ed25519::Ed25519;
|
use crate::sign::ed25519::Ed25519;
|
||||||
use crate::registry::signer::sign::SignatureScheme;
|
use crate::sign::SignatureScheme;
|
||||||
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
|
use crate::s9pk::merkle_archive::directory_contents::DirectoryContents;
|
||||||
use crate::s9pk::merkle_archive::file_contents::FileContents;
|
use crate::s9pk::merkle_archive::file_contents::FileContents;
|
||||||
use crate::s9pk::merkle_archive::sink::Sink;
|
use crate::s9pk::merkle_archive::sink::Sink;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use tokio::fs::File;
|
|||||||
|
|
||||||
use crate::dependencies::DependencyMetadata;
|
use crate::dependencies::DependencyMetadata;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::s9pk::manifest::Manifest;
|
use crate::s9pk::manifest::Manifest;
|
||||||
use crate::s9pk::merkle_archive::sink::Sink;
|
use crate::s9pk::merkle_archive::sink::Sink;
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ pub fn launch(
|
|||||||
if tty {
|
if tty {
|
||||||
use pty_process::blocking as pty_process;
|
use pty_process::blocking as pty_process;
|
||||||
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
||||||
let mut cmd = pty_process::Command::new("/usr/bin/start-cli");
|
let mut cmd = pty_process::Command::new("/usr/bin/start-container");
|
||||||
cmd = cmd.arg("subcontainer").arg("launch-init");
|
cmd = cmd.arg("subcontainer").arg("launch-init");
|
||||||
if let Some(env) = env {
|
if let Some(env) = env {
|
||||||
cmd = cmd.arg("--env").arg(env);
|
cmd = cmd.arg("--env").arg(env);
|
||||||
@@ -339,7 +339,7 @@ pub fn launch(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut cmd = StdCommand::new("/usr/bin/start-cli");
|
let mut cmd = StdCommand::new("/usr/bin/start-container");
|
||||||
cmd.arg("subcontainer").arg("launch-init");
|
cmd.arg("subcontainer").arg("launch-init");
|
||||||
if let Some(env) = env {
|
if let Some(env) = env {
|
||||||
cmd.arg("--env").arg(env);
|
cmd.arg("--env").arg(env);
|
||||||
@@ -534,7 +534,7 @@ pub fn exec(
|
|||||||
if tty {
|
if tty {
|
||||||
use pty_process::blocking as pty_process;
|
use pty_process::blocking as pty_process;
|
||||||
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
let (pty, pts) = pty_process::open().with_kind(ErrorKind::Filesystem)?;
|
||||||
let mut cmd = pty_process::Command::new("/usr/bin/start-cli");
|
let mut cmd = pty_process::Command::new("/usr/bin/start-container");
|
||||||
cmd = cmd.arg("subcontainer").arg("exec-command");
|
cmd = cmd.arg("subcontainer").arg("exec-command");
|
||||||
if let Some(env) = env {
|
if let Some(env) = env {
|
||||||
cmd = cmd.arg("--env").arg(env);
|
cmd = cmd.arg("--env").arg(env);
|
||||||
@@ -589,7 +589,7 @@ pub fn exec(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut cmd = StdCommand::new("/usr/bin/start-cli");
|
let mut cmd = StdCommand::new("/usr/bin/start-container");
|
||||||
cmd.arg("subcontainer").arg("exec-command");
|
cmd.arg("subcontainer").arg("exec-command");
|
||||||
if let Some(env) = env {
|
if let Some(env) = env {
|
||||||
cmd.arg("--env").arg(env);
|
cmd.arg("--env").arg(env);
|
||||||
|
|||||||
@@ -910,7 +910,7 @@ pub async fn attach(
|
|||||||
|
|
||||||
cmd.arg(&*container_id)
|
cmd.arg(&*container_id)
|
||||||
.arg("--")
|
.arg("--")
|
||||||
.arg("start-cli")
|
.arg("start-container")
|
||||||
.arg("subcontainer")
|
.arg("subcontainer")
|
||||||
.arg("exec")
|
.arg("exec")
|
||||||
.arg("--env")
|
.arg("--env")
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use crate::install::PKG_ARCHIVE_DIR;
|
|||||||
use crate::notifications::{notify, NotificationLevel};
|
use crate::notifications::{notify, NotificationLevel};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle, ProgressTrackerWriter};
|
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle, ProgressTrackerWriter};
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::s9pk::merkle_archive::source::FileSource;
|
use crate::s9pk::merkle_archive::source::FileSource;
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use tokio::io::AsyncWrite;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::{Commitment, Digestable};
|
use crate::sign::commitment::{Commitment, Digestable};
|
||||||
use crate::s9pk::merkle_archive::hash::VerifyingWriter;
|
use crate::s9pk::merkle_archive::hash::VerifyingWriter;
|
||||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||||
use crate::util::io::{ParallelBlake3Writer, TrackingIO};
|
use crate::util::io::{ParallelBlake3Writer, TrackingIO};
|
||||||
@@ -4,10 +4,10 @@ use tokio::io::AsyncWrite;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::{Commitment, Digestable};
|
|
||||||
use crate::s9pk::merkle_archive::source::FileSource;
|
use crate::s9pk::merkle_archive::source::FileSource;
|
||||||
use crate::s9pk::merkle_archive::MerkleArchive;
|
use crate::s9pk::merkle_archive::MerkleArchive;
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
|
use crate::sign::commitment::{Commitment, Digestable};
|
||||||
use crate::util::io::TrackingIO;
|
use crate::util::io::TrackingIO;
|
||||||
use crate::util::serde::Base64;
|
use crate::util::serde::Base64;
|
||||||
|
|
||||||
@@ -13,8 +13,8 @@ use ts_rs::TS;
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::{Commitment, Digestable};
|
|
||||||
use crate::s9pk::merkle_archive::hash::VerifyingWriter;
|
use crate::s9pk::merkle_archive::hash::VerifyingWriter;
|
||||||
|
use crate::sign::commitment::{Commitment, Digestable};
|
||||||
use crate::util::serde::Base64;
|
use crate::util::serde::Base64;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, PartialEq, Eq, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, PartialEq, Eq, TS)]
|
||||||
@@ -2,7 +2,7 @@ use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
|
|||||||
use sha2::Sha512;
|
use sha2::Sha512;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::sign::SignatureScheme;
|
use crate::sign::SignatureScheme;
|
||||||
|
|
||||||
pub struct Ed25519;
|
pub struct Ed25519;
|
||||||
impl SignatureScheme for Ed25519 {
|
impl SignatureScheme for Ed25519 {
|
||||||
@@ -12,10 +12,11 @@ use sha2::Sha512;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::Digestable;
|
use crate::sign::commitment::Digestable;
|
||||||
use crate::registry::signer::sign::ed25519::Ed25519;
|
use crate::sign::ed25519::Ed25519;
|
||||||
use crate::util::serde::{deserialize_from_str, serialize_display};
|
use crate::util::serde::{deserialize_from_str, serialize_display};
|
||||||
|
|
||||||
|
pub mod commitment;
|
||||||
pub mod ed25519;
|
pub mod ed25519;
|
||||||
|
|
||||||
pub trait SignatureScheme {
|
pub trait SignatureScheme {
|
||||||
@@ -60,6 +61,7 @@ pub trait SignatureScheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum AnyScheme {
|
pub enum AnyScheme {
|
||||||
Ed25519(Ed25519),
|
Ed25519(Ed25519),
|
||||||
}
|
}
|
||||||
@@ -118,6 +120,7 @@ impl SignatureScheme for AnyScheme {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, TS)]
|
||||||
#[ts(export, type = "string")]
|
#[ts(export, type = "string")]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum AnySigningKey {
|
pub enum AnySigningKey {
|
||||||
Ed25519(<Ed25519 as SignatureScheme>::SigningKey),
|
Ed25519(<Ed25519 as SignatureScheme>::SigningKey),
|
||||||
}
|
}
|
||||||
@@ -189,6 +192,7 @@ impl Serialize for AnySigningKey {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, TS)]
|
||||||
#[ts(export, type = "string")]
|
#[ts(export, type = "string")]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum AnyVerifyingKey {
|
pub enum AnyVerifyingKey {
|
||||||
Ed25519(<Ed25519 as SignatureScheme>::VerifyingKey),
|
Ed25519(<Ed25519 as SignatureScheme>::VerifyingKey),
|
||||||
}
|
}
|
||||||
@@ -261,6 +265,7 @@ impl ValueParserFactory for AnyVerifyingKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum AnyDigest {
|
pub enum AnyDigest {
|
||||||
Sha512(Sha512),
|
Sha512(Sha512),
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ use crate::util::Invoke;
|
|||||||
|
|
||||||
pub const SSH_DIR: &str = "/home/start9/.ssh";
|
pub const SSH_DIR: &str = "/home/start9/.ssh";
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
pub struct SshKeys(BTreeMap<InternedString, WithTimeData<SshPubKey>>);
|
pub struct SshKeys(BTreeMap<InternedString, WithTimeData<SshPubKey>>);
|
||||||
impl SshKeys {
|
impl SshKeys {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|||||||
226
core/startos/src/tunnel/context.rs
Normal file
226
core/startos/src/tunnel/context.rs
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use imbl_value::InternedString;
|
||||||
|
use patch_db::PatchDb;
|
||||||
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
|
use rpc_toolkit::{CallRemote, Context, Empty};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::broadcast::Sender;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::auth::{check_password, Sessions};
|
||||||
|
use crate::context::config::ContextConfig;
|
||||||
|
use crate::context::{CliContext, RpcContext};
|
||||||
|
use crate::middleware::auth::AuthContext;
|
||||||
|
use crate::middleware::signature::SignatureAuthContext;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::rpc_continuations::{OpenAuthedContinuations, RpcContinuations};
|
||||||
|
use crate::tunnel::{TunnelDatabase, TUNNEL_DEFAULT_PORT};
|
||||||
|
use crate::util::iter::TransposeResultIterExt;
|
||||||
|
use crate::util::sync::SyncMutex;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
pub struct TunnelConfig {
|
||||||
|
#[arg(short = 'c', long = "config")]
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
|
#[arg(short = 'l', long = "listen")]
|
||||||
|
pub tunnel_listen: Option<SocketAddr>,
|
||||||
|
#[arg(short = 'd', long = "datadir")]
|
||||||
|
pub datadir: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
impl ContextConfig for TunnelConfig {
|
||||||
|
fn next(&mut self) -> Option<PathBuf> {
|
||||||
|
self.config.take()
|
||||||
|
}
|
||||||
|
fn merge_with(&mut self, other: Self) {
|
||||||
|
self.tunnel_listen = self.tunnel_listen.take().or(other.tunnel_listen);
|
||||||
|
self.datadir = self.datadir.take().or(other.datadir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TunnelConfig {
|
||||||
|
pub fn load(mut self) -> Result<Self, Error> {
|
||||||
|
let path = self.next();
|
||||||
|
self.load_path_rec(path)?;
|
||||||
|
self.load_path_rec(Some("/etc/start-tunneld"))?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TunnelContextSeed {
|
||||||
|
pub listen: SocketAddr,
|
||||||
|
pub addrs: BTreeSet<IpAddr>,
|
||||||
|
pub db: TypedPatchDb<TunnelDatabase>,
|
||||||
|
pub datadir: PathBuf,
|
||||||
|
pub rpc_continuations: RpcContinuations,
|
||||||
|
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
|
||||||
|
pub ephemeral_sessions: SyncMutex<Sessions>,
|
||||||
|
pub shutdown: Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TunnelContext(Arc<TunnelContextSeed>);
|
||||||
|
impl TunnelContext {
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn init(config: &TunnelConfig) -> Result<Self, Error> {
|
||||||
|
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||||
|
let datadir = config
|
||||||
|
.datadir
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_else(|| Path::new("/var/lib/start-tunnel"))
|
||||||
|
.to_owned();
|
||||||
|
if tokio::fs::metadata(&datadir).await.is_err() {
|
||||||
|
tokio::fs::create_dir_all(&datadir).await?;
|
||||||
|
}
|
||||||
|
let db_path = datadir.join("tunnel.db");
|
||||||
|
let db = TypedPatchDb::<TunnelDatabase>::load_or_init(
|
||||||
|
PatchDb::open(&db_path).await?,
|
||||||
|
|| async { Ok(Default::default()) },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let listen = config.tunnel_listen.unwrap_or(SocketAddr::new(
|
||||||
|
Ipv6Addr::UNSPECIFIED.into(),
|
||||||
|
TUNNEL_DEFAULT_PORT,
|
||||||
|
));
|
||||||
|
Ok(Self(Arc::new(TunnelContextSeed {
|
||||||
|
listen,
|
||||||
|
addrs: crate::net::utils::all_socket_addrs_for(listen.port())
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, a)| a.ip())
|
||||||
|
.collect(),
|
||||||
|
db,
|
||||||
|
datadir,
|
||||||
|
rpc_continuations: RpcContinuations::new(),
|
||||||
|
open_authed_continuations: OpenAuthedContinuations::new(),
|
||||||
|
ephemeral_sessions: SyncMutex::new(Sessions::new()),
|
||||||
|
shutdown,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<RpcContinuations> for TunnelContext {
|
||||||
|
fn as_ref(&self) -> &RpcContinuations {
|
||||||
|
&self.rpc_continuations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context for TunnelContext {}
|
||||||
|
impl Deref for TunnelContext {
|
||||||
|
type Target = TunnelContextSeed;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Parser)]
|
||||||
|
pub struct TunnelAddrParams {
|
||||||
|
pub tunnel: IpAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatureAuthContext for TunnelContext {
|
||||||
|
type Database = TunnelDatabase;
|
||||||
|
type AdditionalMetadata = ();
|
||||||
|
type CheckPubkeyRes = ();
|
||||||
|
fn db(&self) -> &TypedPatchDb<Self::Database> {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
async fn sig_context(
|
||||||
|
&self,
|
||||||
|
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
||||||
|
self.addrs
|
||||||
|
.iter()
|
||||||
|
.filter(|a| !match a {
|
||||||
|
IpAddr::V4(a) => a.is_loopback() || a.is_unspecified(),
|
||||||
|
IpAddr::V6(a) => a.is_loopback() || a.is_unspecified(),
|
||||||
|
})
|
||||||
|
.map(|a| InternedString::from_display(&a))
|
||||||
|
.map(Ok)
|
||||||
|
}
|
||||||
|
fn check_pubkey(
|
||||||
|
db: &Model<Self::Database>,
|
||||||
|
pubkey: Option<&crate::sign::AnyVerifyingKey>,
|
||||||
|
_: Self::AdditionalMetadata,
|
||||||
|
) -> Result<Self::CheckPubkeyRes, Error> {
|
||||||
|
if let Some(pubkey) = pubkey {
|
||||||
|
if db.as_auth_pubkeys().de()?.contains(pubkey) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Developer Key is not authorized"),
|
||||||
|
ErrorKind::IncorrectPassword,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
async fn post_auth_hook(
|
||||||
|
&self,
|
||||||
|
_: Self::CheckPubkeyRes,
|
||||||
|
_: &rpc_toolkit::RpcRequest,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AuthContext for TunnelContext {
|
||||||
|
const LOCAL_AUTH_COOKIE_PATH: &str = "/run/start-tunnel/rpc.authcookie";
|
||||||
|
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:root";
|
||||||
|
fn access_sessions(db: &mut Model<Self::Database>) -> &mut Model<crate::auth::Sessions> {
|
||||||
|
db.as_sessions_mut()
|
||||||
|
}
|
||||||
|
fn ephemeral_sessions(&self) -> &SyncMutex<Sessions> {
|
||||||
|
&self.ephemeral_sessions
|
||||||
|
}
|
||||||
|
fn open_authed_continuations(&self) -> &OpenAuthedContinuations<Option<InternedString>> {
|
||||||
|
&self.open_authed_continuations
|
||||||
|
}
|
||||||
|
fn check_password(db: &Model<Self::Database>, password: &str) -> Result<(), Error> {
|
||||||
|
check_password(&db.as_password().de()?, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallRemote<TunnelContext> for CliContext {
|
||||||
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
mut method: &str,
|
||||||
|
params: Value,
|
||||||
|
_: Empty,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
|
let tunnel_addr = if let Some(addr) = self.tunnel_addr {
|
||||||
|
addr
|
||||||
|
} else if let Some(addr) = self.tunnel_listen {
|
||||||
|
addr
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(eyre!("`--tunnel` required"), ErrorKind::InvalidRequest).into());
|
||||||
|
};
|
||||||
|
let sig_addr = self.tunnel_listen.unwrap_or(tunnel_addr);
|
||||||
|
let url = format!("https://{tunnel_addr}").parse()?;
|
||||||
|
|
||||||
|
method = method.strip_prefix("tunnel.").unwrap_or(method);
|
||||||
|
|
||||||
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
url,
|
||||||
|
&InternedString::from_display(&sig_addr.ip()),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallRemote<TunnelContext, TunnelAddrParams> for RpcContext {
|
||||||
|
async fn call_remote(
|
||||||
|
&self,
|
||||||
|
mut method: &str,
|
||||||
|
params: Value,
|
||||||
|
TunnelAddrParams { tunnel }: TunnelAddrParams,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
180
core/startos/src/tunnel/db.rs
Normal file
180
core/startos/src/tunnel/db.rs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use patch_db::json_ptr::{JsonPointer, ROOT};
|
||||||
|
use patch_db::Dump;
|
||||||
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
|
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::instrument;
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::tunnel::context::TunnelContext;
|
||||||
|
use crate::tunnel::TunnelDatabase;
|
||||||
|
use crate::util::serde::{apply_expr, HandlerExtSerde};
|
||||||
|
|
||||||
|
pub fn db_api<C: Context>() -> ParentHandler<C> {
|
||||||
|
ParentHandler::new()
|
||||||
|
.subcommand(
|
||||||
|
"dump",
|
||||||
|
from_fn_async(cli_dump)
|
||||||
|
.with_display_serializable()
|
||||||
|
.with_about("Filter/query db to display tables and records"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"dump",
|
||||||
|
from_fn_async(dump)
|
||||||
|
.with_metadata("admin", Value::Bool(true))
|
||||||
|
.no_cli(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"apply",
|
||||||
|
from_fn_async(cli_apply)
|
||||||
|
.no_display()
|
||||||
|
.with_about("Update a db record"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"apply",
|
||||||
|
from_fn_async(apply)
|
||||||
|
.with_metadata("admin", Value::Bool(true))
|
||||||
|
.no_cli(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
pub struct CliDumpParams {
|
||||||
|
#[arg(long = "pointer", short = 'p')]
|
||||||
|
pointer: Option<JsonPointer>,
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn cli_dump(
|
||||||
|
HandlerArgs {
|
||||||
|
context,
|
||||||
|
parent_method,
|
||||||
|
method,
|
||||||
|
params: CliDumpParams { pointer, path },
|
||||||
|
..
|
||||||
|
}: HandlerArgs<CliContext, CliDumpParams>,
|
||||||
|
) -> Result<Dump, RpcError> {
|
||||||
|
let dump = if let Some(path) = path {
|
||||||
|
PatchDb::open(path).await?.dump(&ROOT).await
|
||||||
|
} else {
|
||||||
|
let method = parent_method.into_iter().chain(method).join(".");
|
||||||
|
from_value::<Dump>(
|
||||||
|
context
|
||||||
|
.call_remote::<TunnelContext>(&method, imbl_value::json!({ "pointer": pointer }))
|
||||||
|
.await?,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(dump)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
pub struct DumpParams {
|
||||||
|
#[arg(long = "pointer", short = 'p')]
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
pointer: Option<JsonPointer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn dump(ctx: TunnelContext, DumpParams { pointer }: DumpParams) -> Result<Dump, Error> {
|
||||||
|
Ok(ctx
|
||||||
|
.db
|
||||||
|
.dump(&pointer.as_ref().map_or(ROOT, |p| p.borrowed()))
|
||||||
|
.await)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
pub struct CliApplyParams {
|
||||||
|
expr: String,
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn cli_apply(
|
||||||
|
HandlerArgs {
|
||||||
|
context,
|
||||||
|
parent_method,
|
||||||
|
method,
|
||||||
|
params: CliApplyParams { expr, path },
|
||||||
|
..
|
||||||
|
}: HandlerArgs<CliContext, CliApplyParams>,
|
||||||
|
) -> Result<(), RpcError> {
|
||||||
|
if let Some(path) = path {
|
||||||
|
PatchDb::open(path)
|
||||||
|
.await?
|
||||||
|
.apply_function(|db| {
|
||||||
|
let res = apply_expr(
|
||||||
|
serde_json::to_value(patch_db::Value::from(db))
|
||||||
|
.with_kind(ErrorKind::Deserialization)?
|
||||||
|
.into(),
|
||||||
|
&expr,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok::<_, Error>((
|
||||||
|
to_value(
|
||||||
|
&serde_json::from_value::<TunnelDatabase>(res.clone().into()).with_ctx(
|
||||||
|
|_| {
|
||||||
|
(
|
||||||
|
crate::ErrorKind::Deserialization,
|
||||||
|
"result does not match database model",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
)?,
|
||||||
|
(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
} else {
|
||||||
|
let method = parent_method.into_iter().chain(method).join(".");
|
||||||
|
context
|
||||||
|
.call_remote::<TunnelContext>(&method, imbl_value::json!({ "expr": expr }))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
pub struct ApplyParams {
|
||||||
|
expr: String,
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn apply(ctx: TunnelContext, ApplyParams { expr, .. }: ApplyParams) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
let res = apply_expr(
|
||||||
|
serde_json::to_value(patch_db::Value::from(db.clone()))
|
||||||
|
.with_kind(ErrorKind::Deserialization)?
|
||||||
|
.into(),
|
||||||
|
&expr,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
db.ser(
|
||||||
|
&serde_json::from_value::<TunnelDatabase>(res.clone().into()).with_ctx(|_| {
|
||||||
|
(
|
||||||
|
crate::ErrorKind::Deserialization,
|
||||||
|
"result does not match database model",
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result
|
||||||
|
}
|
||||||
99
core/startos/src/tunnel/mod.rs
Normal file
99
core/startos/src/tunnel/mod.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
use futures::future::ready;
|
||||||
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, Server};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::auth::Sessions;
|
||||||
|
use crate::middleware::auth::Auth;
|
||||||
|
use crate::middleware::cors::Cors;
|
||||||
|
use crate::net::static_server::{bad_request, not_found, server_error};
|
||||||
|
use crate::net::web_server::{Accept, WebServer};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::rpc_continuations::Guid;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
|
use crate::tunnel::context::TunnelContext;
|
||||||
|
|
||||||
|
pub mod context;
|
||||||
|
pub mod db;
|
||||||
|
|
||||||
|
pub const TUNNEL_DEFAULT_PORT: u16 = 5960;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct TunnelDatabase {
|
||||||
|
pub sessions: Sessions,
|
||||||
|
pub password: String,
|
||||||
|
pub auth_pubkeys: HashSet<AnyVerifyingKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
||||||
|
ParentHandler::new().subcommand(
|
||||||
|
"db",
|
||||||
|
db::db_api::<C>().with_about("Commands to interact with the db i.e. dump and apply"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tunnel_router(ctx: TunnelContext) -> Router {
|
||||||
|
use axum::extract as x;
|
||||||
|
use axum::routing::{any, get};
|
||||||
|
Router::new()
|
||||||
|
.route("/rpc/{*path}", {
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
any(
|
||||||
|
Server::new(move || ready(Ok(ctx.clone())), tunnel_api())
|
||||||
|
.middleware(Cors::new())
|
||||||
|
.middleware(Auth::new())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.route(
|
||||||
|
"/ws/rpc/{*path}",
|
||||||
|
get({
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
move |x::Path(path): x::Path<String>,
|
||||||
|
ws: axum::extract::ws::WebSocketUpgrade| async move {
|
||||||
|
match Guid::from(&path) {
|
||||||
|
None => {
|
||||||
|
tracing::debug!("No Guid Path");
|
||||||
|
bad_request()
|
||||||
|
}
|
||||||
|
Some(guid) => match ctx.rpc_continuations.get_ws_handler(&guid).await {
|
||||||
|
Some(cont) => ws.on_upgrade(cont),
|
||||||
|
_ => not_found(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/rest/rpc/{*path}",
|
||||||
|
any({
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
move |request: x::Request| async move {
|
||||||
|
let path = request
|
||||||
|
.uri()
|
||||||
|
.path()
|
||||||
|
.strip_prefix("/rest/rpc/")
|
||||||
|
.unwrap_or_default();
|
||||||
|
match Guid::from(&path) {
|
||||||
|
None => {
|
||||||
|
tracing::debug!("No Guid Path");
|
||||||
|
bad_request()
|
||||||
|
}
|
||||||
|
Some(guid) => match ctx.rpc_continuations.get_rest_handler(&guid).await {
|
||||||
|
None => not_found(),
|
||||||
|
Some(cont) => cont(request).await.unwrap_or_else(server_error),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Accept + Send + Sync + 'static> WebServer<A> {
|
||||||
|
pub fn serve_tunnel(&mut self, ctx: TunnelContext) {
|
||||||
|
self.serve_router(tunnel_router(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,8 +33,8 @@ use crate::registry::asset::RegistryAsset;
|
|||||||
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
use crate::registry::os::SIG_CONTEXT;
|
use crate::registry::os::SIG_CONTEXT;
|
||||||
use crate::registry::signer::commitment::blake3::Blake3Commitment;
|
use crate::sign::commitment::blake3::Blake3Commitment;
|
||||||
use crate::registry::signer::commitment::Commitment;
|
use crate::sign::commitment::Commitment;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
use crate::sound::{
|
use crate::sound::{
|
||||||
|
|||||||
31
core/startos/src/util/iter.rs
Normal file
31
core/startos/src/util/iter.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
pub trait TransposeResultIterExt: Sized {
|
||||||
|
type Ok: IntoIterator;
|
||||||
|
type Err;
|
||||||
|
fn transpose(self) -> TransposeResult<Self::Ok, Self::Err>;
|
||||||
|
}
|
||||||
|
impl<T: IntoIterator, E> TransposeResultIterExt for Result<T, E> {
|
||||||
|
type Ok = T;
|
||||||
|
type Err = E;
|
||||||
|
fn transpose(self) -> TransposeResult<T, E> {
|
||||||
|
TransposeResult(Some(self.map(|t| t.into_iter())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TransposeResult<T: IntoIterator, E>(Option<Result<T::IntoIter, E>>);
|
||||||
|
impl<T, E> Iterator for TransposeResult<T, E>
|
||||||
|
where
|
||||||
|
T: IntoIterator,
|
||||||
|
{
|
||||||
|
type Item = Result<T::Item, E>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.0.as_ref().map_or(true, |r| r.is_err()) {
|
||||||
|
self.0
|
||||||
|
.take()
|
||||||
|
.map(|e| Err(e.map_or_else(|e| e, |_| unreachable!())))
|
||||||
|
} else if let Some(Ok(res)) = &mut self.0 {
|
||||||
|
res.next().map(Ok)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ pub mod crypto;
|
|||||||
pub mod future;
|
pub mod future;
|
||||||
pub mod http_reader;
|
pub mod http_reader;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
pub mod iter;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod lshw;
|
pub mod lshw;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
|
|||||||
@@ -293,7 +293,8 @@ impl VersionT for Version {
|
|||||||
let mut value = json!({});
|
let mut value = json!({});
|
||||||
value["keyStore"] = to_value(&KeyStore::new(&account)?)?;
|
value["keyStore"] = to_value(&KeyStore::new(&account)?)?;
|
||||||
value["password"] = to_value(&account.password)?;
|
value["password"] = to_value(&account.password)?;
|
||||||
value["compatS9pkKey"] = to_value(&crate::db::model::private::generate_compat_key())?;
|
value["compatS9pkKey"] =
|
||||||
|
to_value(&crate::db::model::private::generate_developer_key())?;
|
||||||
value["sshPrivkey"] = to_value(Pem::new_ref(&account.ssh_key))?;
|
value["sshPrivkey"] = to_value(Pem::new_ref(&account.ssh_key))?;
|
||||||
value["sshPubkeys"] = to_value(&ssh_keys)?;
|
value["sshPubkeys"] = to_value(&ssh_keys)?;
|
||||||
value["availablePorts"] = to_value(&AvailablePorts::new())?;
|
value["availablePorts"] = to_value(&AvailablePorts::new())?;
|
||||||
@@ -377,7 +378,7 @@ impl VersionT for Version {
|
|||||||
let package_s9pk = tokio::fs::File::open(path).await?;
|
let package_s9pk = tokio::fs::File::open(path).await?;
|
||||||
let file = MultiCursorFile::open(&package_s9pk).await?;
|
let file = MultiCursorFile::open(&package_s9pk).await?;
|
||||||
|
|
||||||
let key = ctx.db.peek().await.into_private().into_compat_s9pk_key();
|
let key = ctx.db.peek().await.into_private().into_developer_key();
|
||||||
ctx.services
|
ctx.services
|
||||||
.install(
|
.install(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
@@ -512,7 +513,7 @@ async fn previous_account_info(pg: &sqlx::Pool<sqlx::Postgres>) -> Result<Accoun
|
|||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)
|
)
|
||||||
.with_ctx(|_| (ErrorKind::Database, "X509::from_pem"))?,
|
.with_ctx(|_| (ErrorKind::Database, "X509::from_pem"))?,
|
||||||
compat_s9pk_key: SigningKey::generate(&mut ssh_key::rand_core::OsRng::default()),
|
developer_key: SigningKey::generate(&mut ssh_key::rand_core::OsRng::default()),
|
||||||
ssh_key: ssh_key::PrivateKey::random(
|
ssh_key: ssh_key::PrivateKey::random(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
ssh_key::Algorithm::Ed25519,
|
ssh_key::Algorithm::Ed25519,
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ impl VersionT for Version {
|
|||||||
let manifest: Manifest = from_value(manifest.clone())?;
|
let manifest: Manifest = from_value(manifest.clone())?;
|
||||||
let id = manifest.id.clone();
|
let id = manifest.id.clone();
|
||||||
let mut s9pk: S9pk<_> = S9pk::new_with_manifest(archive, None, manifest);
|
let mut s9pk: S9pk<_> = S9pk::new_with_manifest(archive, None, manifest);
|
||||||
let s9pk_compat_key = ctx.account.read().await.compat_s9pk_key.clone();
|
let s9pk_compat_key = ctx.account.read().await.developer_key.clone();
|
||||||
s9pk.as_archive_mut()
|
s9pk.as_archive_mut()
|
||||||
.set_signer(s9pk_compat_key, SIG_CONTEXT);
|
.set_signer(s9pk_compat_key, SIG_CONTEXT);
|
||||||
s9pk.serialize(&mut tmp_file, true).await?;
|
s9pk.serialize(&mut tmp_file, true).await?;
|
||||||
|
|||||||
@@ -157,10 +157,14 @@ export class SubContainerOwned<
|
|||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.leaderExited = false
|
this.leaderExited = false
|
||||||
this.leader = cp.spawn("start-cli", ["subcontainer", "launch", rootfs], {
|
this.leader = cp.spawn(
|
||||||
killSignal: "SIGKILL",
|
"start-container",
|
||||||
stdio: "inherit",
|
["subcontainer", "launch", rootfs],
|
||||||
})
|
{
|
||||||
|
killSignal: "SIGKILL",
|
||||||
|
stdio: "inherit",
|
||||||
|
},
|
||||||
|
)
|
||||||
this.leader.on("exit", () => {
|
this.leader.on("exit", () => {
|
||||||
this.leaderExited = true
|
this.leaderExited = true
|
||||||
})
|
})
|
||||||
@@ -407,7 +411,7 @@ export class SubContainerOwned<
|
|||||||
delete options.cwd
|
delete options.cwd
|
||||||
}
|
}
|
||||||
const child = cp.spawn(
|
const child = cp.spawn(
|
||||||
"start-cli",
|
"start-container",
|
||||||
[
|
[
|
||||||
"subcontainer",
|
"subcontainer",
|
||||||
"exec",
|
"exec",
|
||||||
@@ -529,7 +533,7 @@ export class SubContainerOwned<
|
|||||||
await this.killLeader()
|
await this.killLeader()
|
||||||
this.leaderExited = false
|
this.leaderExited = false
|
||||||
this.leader = cp.spawn(
|
this.leader = cp.spawn(
|
||||||
"start-cli",
|
"start-container",
|
||||||
[
|
[
|
||||||
"subcontainer",
|
"subcontainer",
|
||||||
"launch",
|
"launch",
|
||||||
@@ -571,7 +575,7 @@ export class SubContainerOwned<
|
|||||||
delete options.cwd
|
delete options.cwd
|
||||||
}
|
}
|
||||||
return cp.spawn(
|
return cp.spawn(
|
||||||
"start-cli",
|
"start-container",
|
||||||
[
|
[
|
||||||
"subcontainer",
|
"subcontainer",
|
||||||
"exec",
|
"exec",
|
||||||
|
|||||||
Reference in New Issue
Block a user