mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
remove product key from setup flow (#1750)
* remove product key flow from setup * feat: backend turned off encryption + new Id + no package id * implement new encryption scheme in FE * decode response string * crypto not working * update setup wizard closes #1762 * feat: Get the encryption key * fix: Get to recovery * remove old code * fix build * fix: Install works for now * fix bug in config for adding new list items * dismiss action modal on success * clear button in config * wip: Currently broken in avahi mdns * include headers with req/res and refactor patchDB init and usage * fix: Can now run in the main * flatline on failed init * update patch DB * add last-wifi-region to data model even though not used by FE * chore: Fix the start. * wip: Fix wrong order for getting hostname before sql has been created * fix edge case where union keys displayed as new when not new * fix: Can start * last backup color, markdown links always new tab, fix bug with login * refactor to remove WithRevision * resolve circular dep issue * update submodule * fix patch-db * update patchDB * update patch again * escape error * decodeuricomponent * increase proxy buffer size * increase proxy buffer size * fix nginx Co-authored-by: BluJ <mogulslayer@gmail.com> Co-authored-by: BluJ <dragondef@gmail.com> Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -10,6 +10,7 @@ use digest::generic_array::GenericArray;
|
||||
use digest::OutputSizeUser;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, TryFutureExt, TryStreamExt};
|
||||
use josekit::jwk::Jwk;
|
||||
use nix::unistd::{Gid, Uid};
|
||||
use openssl::x509::X509;
|
||||
use patch_db::{DbHandle, LockType};
|
||||
@@ -37,7 +38,7 @@ use crate::disk::mount::filesystem::ReadOnly;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::disk::util::{pvscan, recovery_info, DiskListResponse, EmbassyOsRecoveryInfo};
|
||||
use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::hostname::PRODUCT_KEY_PATH;
|
||||
use crate::hostname::{get_hostname, Hostname};
|
||||
use crate::id::Id;
|
||||
use crate::init::init;
|
||||
use crate::install::PKG_PUBLIC_DIR;
|
||||
@@ -62,7 +63,7 @@ where
|
||||
Ok(password)
|
||||
}
|
||||
|
||||
#[command(subcommands(status, disk, attach, execute, recovery, cifs, complete))]
|
||||
#[command(subcommands(status, disk, attach, execute, recovery, cifs, complete, get_secret))]
|
||||
pub fn setup() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -70,14 +71,12 @@ pub fn setup() -> Result<(), Error> {
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct StatusRes {
|
||||
product_key: bool,
|
||||
migrating: bool,
|
||||
}
|
||||
|
||||
#[command(rpc_only, metadata(authenticated = false))]
|
||||
pub async fn status(#[context] ctx: SetupContext) -> Result<StatusRes, Error> {
|
||||
Ok(StatusRes {
|
||||
product_key: tokio::fs::metadata(PRODUCT_KEY_PATH).await.is_ok(),
|
||||
migrating: ctx.recovery_status.read().await.is_some(),
|
||||
})
|
||||
}
|
||||
@@ -123,31 +122,7 @@ pub async fn attach(
|
||||
ErrorKind::DiskManagement,
|
||||
));
|
||||
}
|
||||
let product_key = ctx.product_key().await?;
|
||||
let product_key_path = Path::new("/embassy-data/main/product_key.txt");
|
||||
if tokio::fs::metadata(product_key_path).await.is_ok() {
|
||||
let pkey = Arc::new(
|
||||
tokio::fs::read_to_string(product_key_path)
|
||||
.await?
|
||||
.trim()
|
||||
.to_owned(),
|
||||
);
|
||||
if pkey != product_key {
|
||||
crate::disk::main::export(&*guid, &ctx.datadir).await?;
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"The EmbassyOS product key does not match the supplied drive: {}",
|
||||
pkey
|
||||
),
|
||||
ErrorKind::ProductKeyMismatch,
|
||||
));
|
||||
}
|
||||
}
|
||||
init(
|
||||
&RpcContextConfig::load(ctx.config_path.as_ref()).await?,
|
||||
&*product_key,
|
||||
)
|
||||
.await?;
|
||||
init(&RpcContextConfig::load(ctx.config_path.as_ref()).await?).await?;
|
||||
let secrets = ctx.secret_store().await?;
|
||||
let db = ctx.db(&secrets).await?;
|
||||
let mut secrets_handle = secrets.acquire().await?;
|
||||
@@ -168,16 +143,17 @@ pub async fn attach(
|
||||
|
||||
let tor_key = crate::net::tor::os_key(&mut secrets_tx).await?;
|
||||
|
||||
db_tx.commit(None).await?;
|
||||
db_tx.commit().await?;
|
||||
secrets_tx.commit().await?;
|
||||
let hostname = get_hostname(&mut db_handle).await?;
|
||||
|
||||
let (_, root_ca) = SslManager::init(secrets).await?.export_root_ca().await?;
|
||||
let (_, root_ca) = SslManager::init(secrets, &mut db_handle)
|
||||
.await?
|
||||
.export_root_ca()
|
||||
.await?;
|
||||
let setup_result = SetupResult {
|
||||
tor_address: format!("http://{}", tor_key.public().get_onion_address()),
|
||||
lan_address: format!(
|
||||
"https://embassy-{}.local",
|
||||
crate::hostname::derive_id(&*product_key)
|
||||
),
|
||||
lan_address: hostname.lan_address(),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
};
|
||||
*ctx.setup_result.write().await = Some((guid, setup_result.clone()));
|
||||
@@ -222,6 +198,29 @@ pub async fn recovery_status(
|
||||
ctx.recovery_status.read().await.clone().transpose()
|
||||
}
|
||||
|
||||
/// We want to be able to get a secret, a shared private key with the frontend
|
||||
/// This way the frontend can send a secret, like the password for the setup/ recovory
|
||||
/// without knowing the password over clearnet. We use the public key shared across the network
|
||||
/// since it is fine to share the public, and encrypt against the public.
|
||||
#[command(rename = "get-secret", rpc_only, metadata(authenticated = false))]
|
||||
pub async fn get_secret(
|
||||
#[context] ctx: SetupContext,
|
||||
#[arg] pubkey: Jwk,
|
||||
) -> Result<String, RpcError> {
|
||||
let secret = ctx.update_secret().await?;
|
||||
let mut header = josekit::jwe::JweHeader::new();
|
||||
header.set_algorithm("ECDH-ES");
|
||||
header.set_content_encryption("A256GCM");
|
||||
|
||||
let encrypter = josekit::jwe::alg::ecdh_es::EcdhEsJweAlgorithm::EcdhEs
|
||||
.encrypter_from_jwk(&pubkey)
|
||||
.unwrap();
|
||||
|
||||
Ok(josekit::jwe::serialize_compact(secret.as_bytes(), &header, &encrypter).unwrap())
|
||||
// Need to encrypt from the public key sent
|
||||
// then encode via hex
|
||||
}
|
||||
|
||||
#[command(subcommands(verify_cifs))]
|
||||
pub fn cifs() -> Result<(), Error> {
|
||||
Ok(())
|
||||
@@ -269,14 +268,11 @@ pub async fn execute(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((tor_addr, root_ca)) => {
|
||||
Ok((hostname, tor_addr, root_ca)) => {
|
||||
tracing::info!("Setup Successful! Tor Address: {}", tor_addr);
|
||||
Ok(SetupResult {
|
||||
tor_address: format!("http://{}", tor_addr),
|
||||
lan_address: format!(
|
||||
"https://embassy-{}.local",
|
||||
crate::hostname::derive_id(&ctx.product_key().await?)
|
||||
),
|
||||
lan_address: hostname.lan_address(),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
})
|
||||
}
|
||||
@@ -299,33 +295,14 @@ pub async fn complete(#[context] ctx: SetupContext) -> Result<SetupResult, Error
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
if tokio::fs::metadata(PRODUCT_KEY_PATH).await.is_err() {
|
||||
crate::hostname::set_product_key(&*ctx.product_key().await?).await?;
|
||||
} else {
|
||||
let key_on_disk = crate::hostname::get_product_key().await?;
|
||||
let key_in_cache = ctx.product_key().await?;
|
||||
if *key_in_cache != key_on_disk {
|
||||
crate::hostname::set_product_key(&*ctx.product_key().await?).await?;
|
||||
}
|
||||
}
|
||||
tokio::fs::write(
|
||||
Path::new("/embassy-data/main/product_key.txt"),
|
||||
&*ctx.product_key().await?,
|
||||
)
|
||||
.await?;
|
||||
let secrets = ctx.secret_store().await?;
|
||||
let mut db = ctx.db(&secrets).await?.handle();
|
||||
let hostname = crate::hostname::get_hostname().await?;
|
||||
let hostname = crate::hostname::get_hostname(&mut db).await?;
|
||||
let si = crate::db::DatabaseModel::new().server_info();
|
||||
si.clone()
|
||||
.id()
|
||||
.put(&mut db, &crate::hostname::get_id().await?)
|
||||
.await?;
|
||||
let id = crate::hostname::get_id(&mut db).await?;
|
||||
si.clone().id().put(&mut db, &id).await?;
|
||||
si.lan_address()
|
||||
.put(
|
||||
&mut db,
|
||||
&format!("https://{}.local", &hostname).parse().unwrap(),
|
||||
)
|
||||
.put(&mut db, &hostname.lan_address().parse().unwrap())
|
||||
.await?;
|
||||
let mut guid_file = File::create("/embassy-os/disk.guid").await?;
|
||||
guid_file.write_all(guid.as_bytes()).await?;
|
||||
@@ -341,7 +318,7 @@ pub async fn execute_inner(
|
||||
embassy_password: String,
|
||||
recovery_source: Option<BackupTargetFS>,
|
||||
recovery_password: Option<String>,
|
||||
) -> Result<(OnionAddressV3, X509), Error> {
|
||||
) -> Result<(Hostname, OnionAddressV3, X509), Error> {
|
||||
if ctx.recovery_status.read().await.is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("Cannot execute setup while in recovery!"),
|
||||
@@ -374,12 +351,11 @@ pub async fn execute_inner(
|
||||
recovery_password,
|
||||
)
|
||||
.await?;
|
||||
init(
|
||||
&RpcContextConfig::load(ctx.config_path.as_ref()).await?,
|
||||
&ctx.product_key().await?,
|
||||
)
|
||||
.await?;
|
||||
let res = (tor_addr, root_ca.clone());
|
||||
let db = init(&RpcContextConfig::load(ctx.config_path.as_ref()).await?)
|
||||
.await?
|
||||
.db;
|
||||
let hostname = get_hostname(&mut db.handle()).await?;
|
||||
let res = (hostname.clone(), tor_addr, root_ca.clone());
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = recover_fut
|
||||
.and_then(|_| async {
|
||||
@@ -387,10 +363,7 @@ pub async fn execute_inner(
|
||||
guid,
|
||||
SetupResult {
|
||||
tor_address: format!("http://{}", tor_addr),
|
||||
lan_address: format!(
|
||||
"https://embassy-{}.local",
|
||||
crate::hostname::derive_id(&ctx.product_key().await?)
|
||||
),
|
||||
lan_address: hostname.lan_address(),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
},
|
||||
));
|
||||
@@ -412,23 +385,19 @@ pub async fn execute_inner(
|
||||
res
|
||||
} else {
|
||||
let (tor_addr, root_ca) = fresh_setup(&ctx, &embassy_password).await?;
|
||||
init(
|
||||
&RpcContextConfig::load(ctx.config_path.as_ref()).await?,
|
||||
&ctx.product_key().await?,
|
||||
)
|
||||
.await?;
|
||||
let db = init(&RpcContextConfig::load(ctx.config_path.as_ref()).await?)
|
||||
.await?
|
||||
.db;
|
||||
*ctx.setup_result.write().await = Some((
|
||||
guid,
|
||||
SetupResult {
|
||||
tor_address: format!("http://{}", tor_addr),
|
||||
lan_address: format!(
|
||||
"https://embassy-{}.local",
|
||||
crate::hostname::derive_id(&ctx.product_key().await?)
|
||||
),
|
||||
lan_address: get_hostname(&mut db.handle()).await?.lan_address(),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
},
|
||||
));
|
||||
(tor_addr, root_ca)
|
||||
let hostname = get_hostname(&mut db.handle()).await?;
|
||||
(hostname, tor_addr, root_ca)
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
@@ -455,7 +424,8 @@ async fn fresh_setup(
|
||||
)
|
||||
.execute(&mut sqlite_pool.acquire().await?)
|
||||
.await?;
|
||||
let (_, root_ca) = SslManager::init(sqlite_pool.clone())
|
||||
let db = ctx.db(&sqlite_pool).await?;
|
||||
let (_, root_ca) = SslManager::init(sqlite_pool.clone(), &mut db.handle())
|
||||
.await?
|
||||
.export_root_ca()
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user