mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
begin setup flow
This commit is contained in:
committed by
Aiden McClelland
parent
6c68c7fed9
commit
8f362e7d1e
80
appmgr/Cargo.lock
generated
80
appmgr/Cargo.lock
generated
@@ -2,6 +2,19 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cipher",
|
||||
"cpufeatures 0.2.1",
|
||||
"ctr",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.4"
|
||||
@@ -122,6 +135,12 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
|
||||
|
||||
[[package]]
|
||||
name = "basic-cookies"
|
||||
version = "0.1.4"
|
||||
@@ -345,6 +364,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.2.1"
|
||||
@@ -468,6 +496,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "2.0.0"
|
||||
@@ -589,6 +626,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
@@ -759,6 +805,7 @@ dependencies = [
|
||||
name = "embassy-os"
|
||||
version = "0.3.0-pre.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"avahi-sys",
|
||||
@@ -777,6 +824,7 @@ dependencies = [
|
||||
"futures",
|
||||
"git-version",
|
||||
"hex",
|
||||
"hmac",
|
||||
"http",
|
||||
"hyper-ws-listener",
|
||||
"indexmap",
|
||||
@@ -790,6 +838,7 @@ dependencies = [
|
||||
"openssh-keys",
|
||||
"openssl",
|
||||
"patch-db",
|
||||
"pbkdf2",
|
||||
"pin-project",
|
||||
"prettytable-rs",
|
||||
"proptest",
|
||||
@@ -1504,9 +1553,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@@ -1835,6 +1884,17 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ad7268ef9bc463fddde8361d358fbfae1aeeb1fb62eca111cd8c763bf1c5891"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "patch-db"
|
||||
version = "0.1.0"
|
||||
@@ -1875,6 +1935,18 @@ dependencies = [
|
||||
"syn 1.0.75",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"hmac",
|
||||
"password-hash",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
@@ -2665,7 +2737,7 @@ checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.1.5",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
@@ -2684,7 +2756,7 @@ checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.1.5",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
@@ -43,6 +43,7 @@ portable = []
|
||||
production = []
|
||||
|
||||
[dependencies]
|
||||
aes = { version = "0.7.5", features = ["ctr", "armv8"] }
|
||||
anyhow = "1.0.40"
|
||||
async-trait = "0.1.42"
|
||||
avahi-sys = { git = "https://github.com/Start9Labs/avahi-sys", version = "0.10.0", branch = "feature/dynamic-linking", features = [
|
||||
@@ -62,6 +63,7 @@ fd-lock-rs = "0.1.3"
|
||||
futures = "0.3.8"
|
||||
git-version = "0.3.4"
|
||||
hex = "0.4.3"
|
||||
hmac = "0.11.0"
|
||||
http = "0.2.3"
|
||||
hyper-ws-listener = { git = "https://github.com/Start9Labs/hyper-ws-listener.git", branch = "main" }
|
||||
indexmap = { version = "1.6.2", features = ["serde"] }
|
||||
@@ -75,6 +77,7 @@ nix = "0.22.0"
|
||||
openssh-keys = "0.5.0"
|
||||
openssl = { version = "0.10.30", features = ["vendored"] }
|
||||
patch-db = { version = "*", path = "../../patch-db/patch-db" }
|
||||
pbkdf2 = "0.9.0"
|
||||
pin-project = "1.0.6"
|
||||
prettytable-rs = "0.8.0"
|
||||
proptest = "1.0.0"
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use embassy::context::rpc::RpcContextConfig;
|
||||
use embassy::context::{RecoveryContext, SetupContext};
|
||||
use embassy::hostname::get_product_key;
|
||||
use embassy::middleware::encrypt::encrypt;
|
||||
use embassy::util::Invoke;
|
||||
use embassy::{Error, ResultExt};
|
||||
use http::StatusCode;
|
||||
@@ -14,6 +17,7 @@ fn status_fn(_: i32) -> StatusCode {
|
||||
|
||||
async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
|
||||
let cfg = RpcContextConfig::load(cfg_path).await?;
|
||||
embassy::disk::util::mount("LABEL=EMBASSY", "/embassy-os").await?;
|
||||
if tokio::fs::metadata("/embassy-os/disk.guid").await.is_ok() {
|
||||
embassy::disk::main::load(
|
||||
&cfg,
|
||||
@@ -26,11 +30,14 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
|
||||
log::info!("Loaded Disk");
|
||||
} else {
|
||||
let ctx = SetupContext::init(cfg_path).await?;
|
||||
let encrypt = encrypt(Arc::new(get_product_key().await?));
|
||||
rpc_server!({
|
||||
command: embassy::setup_api,
|
||||
context: ctx.clone(),
|
||||
status: status_fn,
|
||||
middleware: [ ]
|
||||
middleware: [
|
||||
encrypt,
|
||||
]
|
||||
})
|
||||
.with_graceful_shutdown({
|
||||
let mut shutdown = ctx.shutdown.subscribe();
|
||||
@@ -65,6 +72,11 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
|
||||
.invoke(embassy::ErrorKind::Filesystem)
|
||||
.await?;
|
||||
embassy::disk::util::bind(&tmp_docker, "/var/lib/docker", false).await?;
|
||||
Command::new("systemctl")
|
||||
.arg("restart")
|
||||
.arg("docker")
|
||||
.invoke(embassy::ErrorKind::Journald)
|
||||
.await?;
|
||||
log::info!("Mounted Docker Data");
|
||||
embassy::ssh::sync_keys_from_db(&secret_store, "/root/.ssh/authorized_keys").await?;
|
||||
log::info!("Synced SSH Keys");
|
||||
|
||||
215
appmgr/src/middleware/encrypt.rs
Normal file
215
appmgr/src/middleware/encrypt.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use aes::cipher::{CipherKey, NewCipher, Nonce, StreamCipher};
|
||||
use aes::Aes256Ctr;
|
||||
use anyhow::anyhow;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, Stream};
|
||||
use hmac::Hmac;
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use pbkdf2::pbkdf2;
|
||||
use rpc_toolkit::hyper::http::Error as HttpError;
|
||||
use rpc_toolkit::hyper::{self, Body, Request, Response, StatusCode};
|
||||
use rpc_toolkit::rpc_server_helpers::{
|
||||
to_response, DynMiddleware, DynMiddlewareStage2, DynMiddlewareStage3, DynMiddlewareStage4,
|
||||
};
|
||||
use rpc_toolkit::yajrc::RpcMethod;
|
||||
use rpc_toolkit::Metadata;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::util::Apply;
|
||||
use crate::Error;
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct DecryptStream {
|
||||
key: Arc<String>,
|
||||
#[pin]
|
||||
body: Body,
|
||||
ctr: Vec<u8>,
|
||||
salt: Vec<u8>,
|
||||
aes: Option<Aes256Ctr>,
|
||||
}
|
||||
impl DecryptStream {
|
||||
pub fn new(key: Arc<String>, body: Body) -> Self {
|
||||
DecryptStream {
|
||||
key,
|
||||
body,
|
||||
ctr: Vec::new(),
|
||||
salt: Vec::new(),
|
||||
aes: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Stream for DecryptStream {
|
||||
type Item = hyper::Result<hyper::body::Bytes>;
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
let this = self.project();
|
||||
match this.body.poll_next(cx) {
|
||||
std::task::Poll::Pending => std::task::Poll::Pending,
|
||||
std::task::Poll::Ready(Some(Ok(bytes))) => std::task::Poll::Ready(Some(Ok({
|
||||
let mut buf = &*bytes;
|
||||
if let Some(aes) = this.aes.as_mut() {
|
||||
let mut res = buf.to_vec();
|
||||
aes.apply_keystream(&mut res);
|
||||
res.into()
|
||||
} else {
|
||||
if this.ctr.len() < 16 && buf.len() > 0 {
|
||||
let to_read = std::cmp::min(16 - this.ctr.len(), buf.len());
|
||||
this.ctr.extend_from_slice(&buf[0..to_read]);
|
||||
buf = &buf[to_read..];
|
||||
}
|
||||
if this.salt.len() < 16 && buf.len() > 0 {
|
||||
let to_read = std::cmp::min(16 - this.salt.len(), buf.len());
|
||||
this.salt.extend_from_slice(&buf[0..to_read]);
|
||||
buf = &buf[to_read..];
|
||||
}
|
||||
if this.ctr.len() == 16 && this.salt.len() == 16 {
|
||||
let mut aeskey = CipherKey::<Aes256Ctr>::default();
|
||||
pbkdf2::<Hmac<Sha256>>(
|
||||
this.key.as_bytes(),
|
||||
&this.salt,
|
||||
100_000,
|
||||
aeskey.as_mut_slice(),
|
||||
);
|
||||
let ctr = Nonce::<Aes256Ctr>::from_slice(&this.ctr);
|
||||
*this.aes = Some(Aes256Ctr::new(&aeskey, &ctr));
|
||||
buf.to_vec().into()
|
||||
} else {
|
||||
hyper::body::Bytes::new()
|
||||
}
|
||||
}
|
||||
}))),
|
||||
std::task::Poll::Ready(a) => std::task::Poll::Ready(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct EncryptStream {
|
||||
#[pin]
|
||||
body: Body,
|
||||
aes: Aes256Ctr,
|
||||
prefix: Option<[u8; 32]>,
|
||||
}
|
||||
impl EncryptStream {
|
||||
pub fn new(key: &str, body: Body) -> Self {
|
||||
let prefix: [u8; 32] = rand::random();
|
||||
let mut aeskey = CipherKey::<Aes256Ctr>::default();
|
||||
pbkdf2::<Hmac<Sha256>>(
|
||||
key.as_bytes(),
|
||||
&prefix[16..],
|
||||
100_000,
|
||||
aeskey.as_mut_slice(),
|
||||
);
|
||||
let ctr = Nonce::<Aes256Ctr>::from_slice(&prefix[..16]);
|
||||
let aes = Aes256Ctr::new(&aeskey, &ctr);
|
||||
EncryptStream {
|
||||
body,
|
||||
aes,
|
||||
prefix: Some(prefix),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Stream for EncryptStream {
|
||||
type Item = hyper::Result<hyper::body::Bytes>;
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
let this = self.project();
|
||||
if let Some(prefix) = this.prefix.take() {
|
||||
std::task::Poll::Ready(Some(Ok(prefix.to_vec().into())))
|
||||
} else {
|
||||
match this.body.poll_next(cx) {
|
||||
std::task::Poll::Pending => std::task::Poll::Pending,
|
||||
std::task::Poll::Ready(Some(Ok(bytes))) => std::task::Poll::Ready(Some(Ok({
|
||||
let mut res = bytes.to_vec();
|
||||
this.aes.apply_keystream(&mut res);
|
||||
res.into()
|
||||
}))),
|
||||
std::task::Poll::Ready(a) => std::task::Poll::Ready(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encrypted(headers: &HeaderMap) -> bool {
|
||||
headers
|
||||
.get("Content-Encoding")
|
||||
.and_then(|h| {
|
||||
h.to_str()
|
||||
.ok()?
|
||||
.split(",")
|
||||
.any(|s| s == "aesctr256")
|
||||
.apply(Some)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn encrypt<M: Metadata>(key: Arc<String>) -> DynMiddleware<M> {
|
||||
Box::new(
|
||||
move |req: &mut Request<Body>,
|
||||
metadata: M|
|
||||
-> BoxFuture<Result<Result<DynMiddlewareStage2, Response<Body>>, HttpError>> {
|
||||
let key = key.clone();
|
||||
async move {
|
||||
let encrypted = encrypted(req.headers());
|
||||
if encrypted {
|
||||
let body = std::mem::take(req.body_mut());
|
||||
*req.body_mut() = Body::wrap_stream(DecryptStream::new(key.clone(), body));
|
||||
};
|
||||
let res: DynMiddlewareStage2 = Box::new(move |req, rpc_req| {
|
||||
async move {
|
||||
if !encrypted
|
||||
&& metadata
|
||||
.get(&rpc_req.method.as_str(), "encrypted")
|
||||
.unwrap_or_default()
|
||||
{
|
||||
let (res_parts, _) = Response::new(()).into_parts();
|
||||
Ok(Err(to_response(
|
||||
&req.headers,
|
||||
res_parts,
|
||||
Err(Error::new(
|
||||
anyhow!("Must be encrypted"),
|
||||
crate::ErrorKind::Authorization,
|
||||
)
|
||||
.into()),
|
||||
|_| StatusCode::OK,
|
||||
)?))
|
||||
} else {
|
||||
let res: DynMiddlewareStage3 = Box::new(move |_, _| {
|
||||
async move {
|
||||
let res: DynMiddlewareStage4 = Box::new(move |res| {
|
||||
async move {
|
||||
if encrypted {
|
||||
res.headers_mut().insert(
|
||||
"Content-Encoding",
|
||||
HeaderValue::from_static("aesctr256"),
|
||||
);
|
||||
let body = std::mem::take(res.body_mut());
|
||||
*res.body_mut() = Body::wrap_stream(
|
||||
EncryptStream::new(&*key, body),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
});
|
||||
Ok(Ok(res))
|
||||
}
|
||||
.boxed()
|
||||
});
|
||||
Ok(Ok(res))
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
});
|
||||
Ok(Ok(res))
|
||||
}
|
||||
.boxed()
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod auth;
|
||||
pub mod cors;
|
||||
pub mod encrypt;
|
||||
|
||||
@@ -347,14 +347,14 @@ pub async fn daemon<F: FnMut() -> Fut, Fut: Future<Output = ()> + Send + 'static
|
||||
mut shutdown: tokio::sync::broadcast::Receiver<Option<Shutdown>>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
loop {
|
||||
match tokio::spawn(f()).await {
|
||||
Err(e) if e.is_panic() => return Err(anyhow!("daemon panicked!")),
|
||||
_ => (),
|
||||
}
|
||||
tokio::select! {
|
||||
_ = shutdown.recv() => return Ok(()),
|
||||
_ = tokio::time::sleep(cooldown) => (),
|
||||
}
|
||||
match tokio::spawn(f()).await {
|
||||
Err(e) if e.is_panic() => return Err(anyhow!("daemon panicked!")),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user