begin setup flow

This commit is contained in:
Aiden McClelland
2021-09-08 16:18:03 -06:00
committed by Aiden McClelland
parent 6c68c7fed9
commit 8f362e7d1e
6 changed files with 312 additions and 9 deletions

80
appmgr/Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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"

View File

@@ -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");

View 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()
},
)
}

View File

@@ -1,2 +1,3 @@
pub mod auth;
pub mod cors;
pub mod encrypt;

View File

@@ -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!")),
_ => (),
}
}
}