forked from brl/citadel-tools
Compare commits
2 Commits
upgrade_to
...
update_too
Author | SHA1 | Date | |
---|---|---|---|
0ff03cb273 | |||
ae41a71b60 |
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
[env]
|
||||
# overide this with your name to send to a different path on the server
|
||||
UPDATES_CLIENT = "public"
|
||||
UPDATES_CHANNEL = "prod"
|
||||
|
100
Cargo.lock
generated
100
Cargo.lock
generated
@@ -101,9 +101,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.89"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
|
||||
|
||||
[[package]]
|
||||
name = "array-macro"
|
||||
@@ -308,9 +308,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.7.2"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
|
||||
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
@@ -338,9 +338,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.21"
|
||||
version = "1.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0"
|
||||
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -434,6 +434,7 @@ dependencies = [
|
||||
"pwhash",
|
||||
"reqwest",
|
||||
"rpassword",
|
||||
"rs-release",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
@@ -443,9 +444,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.18"
|
||||
version = "4.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
|
||||
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -453,9 +454,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.18"
|
||||
version = "4.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
|
||||
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -465,9 +466,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.18"
|
||||
version = "4.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2 1.0.86",
|
||||
@@ -731,6 +732,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519 2.2.3",
|
||||
"rand_core",
|
||||
"serde",
|
||||
"sha2 0.10.8",
|
||||
"subtle",
|
||||
@@ -1494,9 +1496,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -1642,14 +1644,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.159"
|
||||
version = "0.2.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
|
||||
[[package]]
|
||||
name = "libcitadel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"byteorder",
|
||||
"clap",
|
||||
@@ -2083,9 +2086,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
@@ -2380,6 +2383,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rs-release"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5004605c85f4ba88d5d02ed03d2ea460356322fd3fe4500d9b33d037edd1c5c1"
|
||||
|
||||
[[package]]
|
||||
name = "rtoolbox"
|
||||
version = "0.0.2"
|
||||
@@ -2493,9 +2502,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.12.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
|
||||
checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -2847,7 +2856,7 @@ dependencies = [
|
||||
"proc-macro2 1.0.86",
|
||||
"quote 1.0.37",
|
||||
"syn 1.0.109",
|
||||
"unicode-xid 0.2.6",
|
||||
"unicode-xid 0.2.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2931,18 +2940,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.64"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.64"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.86",
|
||||
"quote 1.0.37",
|
||||
@@ -3029,7 +3038,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.21",
|
||||
"toml_edit 0.22.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3054,9 +3063,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.21"
|
||||
version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -3137,24 +3146,24 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.24"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
|
||||
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
@@ -3164,9 +3173,28 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
|
||||
|
||||
[[package]]
|
||||
name = "update_generator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"ed25519-dalek",
|
||||
"glob",
|
||||
"libcitadel",
|
||||
"rand",
|
||||
"rpassword",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
"sodiumoxide",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["citadel-realms", "citadel-installer-ui", "citadel-tool", "realmsd", "realm-config-ui", "launch-gnome-software" ]
|
||||
members = ["citadel-realms", "citadel-installer-ui", "citadel-tool", "realmsd", "realm-config-ui", "launch-gnome-software", "update-generator" ]
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
2854
citadel-installer-ui/Cargo.lock
generated
2854
citadel-installer-ui/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ pwhash = "1.0"
|
||||
tempfile = "3"
|
||||
ed25519-dalek = {version = "2.1", features = ["pem"]}
|
||||
anyhow = "1.0"
|
||||
rs-release = "0.1"
|
||||
reqwest = {version = "0.12", features = ["blocking"]}
|
||||
glob = "0.3"
|
||||
serde_cbor = "0.11"
|
||||
serde_cbor = "0.11"
|
@@ -8,7 +8,7 @@ use std::path::Path;
|
||||
|
||||
mod live;
|
||||
mod disks;
|
||||
pub mod rootfs;
|
||||
mod rootfs;
|
||||
|
||||
pub fn main(args: Vec<String>) {
|
||||
if CommandLine::debug() {
|
||||
|
@@ -94,7 +94,7 @@ fn choose_revert_partition(best: Option<Partition>) -> Option<Partition> {
|
||||
best
|
||||
}
|
||||
|
||||
pub fn choose_boot_partiton(scan: bool, revert_rootfs: bool) -> Result<Partition> {
|
||||
fn choose_boot_partiton(scan: bool, revert_rootfs: bool) -> Result<Partition> {
|
||||
let mut partitions = Partition::rootfs_partitions()?;
|
||||
|
||||
if scan {
|
||||
|
@@ -1,15 +1,20 @@
|
||||
use crate::{update, Path};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result};
|
||||
use clap::ArgMatches;
|
||||
use ed25519_dalek::{pkcs8::DecodePublicKey, VerifyingKey};
|
||||
use libcitadel::updates::UPDATE_SERVER_HOSTNAME;
|
||||
use libcitadel::ResourceImage;
|
||||
use libcitadel::{updates, updates::CitadelVersionStruct};
|
||||
use libcitadel::{OsRelease, ResourceImage};
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
const OS_RELEASE_PATH: &str = "/etc/os-release";
|
||||
const IMAGE_DIRECTORY_PATH: &str = "/run/citadel/images/";
|
||||
const EXTRA_IMAGE_PATH: &str = "/run/citadel/images/citadel-extra.img";
|
||||
const ROOTFS_IMAGE_PATH: &str = "/run/citadel/images/citadel-rootfs.img";
|
||||
const UPDATE_SERVER_KEY_PATH: &str = "/etc/citadel/update_server_key.pub";
|
||||
const LAST_RESORT_CLIENT: &str = "public";
|
||||
|
||||
pub fn check() -> Result<()> {
|
||||
let current_version = get_current_os_config()?;
|
||||
@@ -25,7 +30,7 @@ pub fn check() -> Result<()> {
|
||||
components_to_upgrade[0]
|
||||
);
|
||||
} else if components_to_upgrade.len() > 1 {
|
||||
println!("We found the following components to upgrade:");
|
||||
println!("We found the following components to upgrade: \n");
|
||||
|
||||
for component in components_to_upgrade {
|
||||
println!("{}", component);
|
||||
@@ -58,7 +63,7 @@ pub fn download(sub_matches: &ArgMatches) -> Result<()> {
|
||||
pub fn read_remote() -> Result<()> {
|
||||
let server_citadel_version = fetch_and_verify_version_cbor(&get_current_os_config()?)?;
|
||||
|
||||
println!("Server offers:\n{server_citadel_version}");
|
||||
println!("Server offers:\n{}", server_citadel_version);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -152,27 +157,57 @@ fn compare_citadel_versions(
|
||||
Ok(update_vec)
|
||||
}
|
||||
|
||||
// We need to get the version of the rootfs, kernel and extra images currently installed
|
||||
fn get_current_os_config() -> Result<updates::CitadelVersionStruct> {
|
||||
let client = OsRelease::citadel_client().context("Failed to find client of current system")?;
|
||||
let channel = OsRelease::citadel_channel().context("Failed to find channel of current system")?;
|
||||
let publisher = OsRelease::citadel_publisher().context("Failed to find publisher of current system")?;
|
||||
fn get_image_version<P: AsRef<Path>>(path: &P) -> Result<String> {
|
||||
let resource_image = ResourceImage::from_path(path)?;
|
||||
|
||||
let metainfo;
|
||||
// choose best partion to boot from as the partition to compare versions with
|
||||
let rootfs_version = match crate::boot::rootfs::choose_boot_partiton(false, false) {
|
||||
Ok(part) => {metainfo = part.header().metainfo();
|
||||
metainfo.version() }
|
||||
Err(e) => bail!("Rootfs version not found. Error: {e}"),
|
||||
Ok(resource_image.metainfo().version().to_string())
|
||||
}
|
||||
|
||||
fn get_current_os_config() -> Result<updates::CitadelVersionStruct> {
|
||||
let os_release = rs_release::parse_os_release(OS_RELEASE_PATH).context(format!(
|
||||
"Failed to get OS_RELEASE_PATH from {}",
|
||||
OS_RELEASE_PATH
|
||||
))?;
|
||||
|
||||
// We need to get the version of the rootfs, kernel and extra images
|
||||
// We first check if there is an env var called UPDATES_CLIENT
|
||||
let client = match std::env::var("UPDATES_CLIENT") {
|
||||
Ok(a) => a,
|
||||
Err(_) => os_release
|
||||
.get("CLIENT")
|
||||
.unwrap_or(&LAST_RESORT_CLIENT.to_string())
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
// Get highest values of image versions
|
||||
let kernel_resource = ResourceImage::find("kernel")?.metainfo();
|
||||
let kernel_version = kernel_resource.version();
|
||||
let channel = os_release.get("CITADEL_CHANNEL");
|
||||
|
||||
// Search image dir for kernel image and extract version
|
||||
let mut kernel_version = String::from("0.0.0");
|
||||
for path in glob::glob(&format!("{}citadel-kernel*.img", IMAGE_DIRECTORY_PATH))
|
||||
.expect("Failed to read glob pattern")
|
||||
{
|
||||
match path {
|
||||
Ok(path) => {
|
||||
kernel_version = get_image_version(&path).context(format!(
|
||||
"Failed to get image version at path: {}",
|
||||
path.display()
|
||||
))?
|
||||
}
|
||||
Err(e) => anyhow::bail!("Failed to find kernel image. Error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
let rootfs_version = get_image_version(&ROOTFS_IMAGE_PATH).context(format!(
|
||||
"Failed to get rootfs img version at path: {}",
|
||||
ROOTFS_IMAGE_PATH
|
||||
))?;
|
||||
let extra_version = get_image_version(&EXTRA_IMAGE_PATH).context(format!(
|
||||
"Failed to get extra img version at path: {}",
|
||||
EXTRA_IMAGE_PATH
|
||||
))?;
|
||||
|
||||
let publisher = os_release.get("CITADEL_PUBLISHER");
|
||||
|
||||
let extra_resource = ResourceImage::find("extra")?.metainfo();
|
||||
let extra_version = extra_resource.version();
|
||||
|
||||
let mut component_version = Vec::new();
|
||||
component_version.push(updates::AvailableComponentVersion {
|
||||
component: updates::Component::Rootfs,
|
||||
@@ -192,9 +227,9 @@ fn get_current_os_config() -> Result<updates::CitadelVersionStruct> {
|
||||
|
||||
let current_version_struct = updates::CitadelVersionStruct {
|
||||
client: client.to_owned(),
|
||||
channel: channel.to_owned(),
|
||||
channel: channel.unwrap_or(&"prod".to_owned()).to_owned(),
|
||||
component_version,
|
||||
publisher: publisher.to_owned(),
|
||||
publisher: publisher.unwrap_or(&"Subgraph".to_string()).to_owned(),
|
||||
};
|
||||
|
||||
Ok(current_version_struct)
|
||||
@@ -210,7 +245,7 @@ fn fetch_and_verify_version_cbor(
|
||||
|
||||
let version_file_bytes = reqwest::blocking::get(&url)?
|
||||
.bytes()
|
||||
.context(format!("Failed to get version_file_bytes from {url}"))?;
|
||||
.context(format!("Failed to get version_file_bytes from {}", url))?;
|
||||
|
||||
let crypto_container: updates::CryptoContainerFile =
|
||||
serde_cbor::from_slice(&version_file_bytes)
|
||||
@@ -231,8 +266,7 @@ fn fetch_and_verify_version_cbor(
|
||||
let signature = ed25519_dalek::Signature::from_str(&crypto_container.signature)?;
|
||||
|
||||
// verify signature
|
||||
public_key.verify_strict(&crypto_container.serialized_citadel_version, &signature)
|
||||
.context("We failed to verify the signature update release file. Please make sure the key at /etc/citade/update_server_key.pub matches the one publicly linked to your update provider.")?;
|
||||
public_key.verify_strict(&crypto_container.serialized_citadel_version, &signature)?;
|
||||
|
||||
// construct the struct
|
||||
let citadel_version_struct: updates::CitadelVersionStruct =
|
||||
@@ -245,7 +279,7 @@ fn prompt_user_for_permission_to_download(
|
||||
component: &updates::AvailableComponentVersion,
|
||||
) -> Result<bool> {
|
||||
println!(
|
||||
"Would you like to download and install the new version of the {} image with version {}? (y/n)",
|
||||
"Would you like to download the new version of the {} image with version {}? (y/n)",
|
||||
component.component, component.version
|
||||
);
|
||||
|
||||
@@ -266,8 +300,9 @@ fn prompt_user_for_permission_to_download(
|
||||
fn download_file(path: &str) -> Result<std::path::PathBuf> {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
let url = format!("https://{UPDATE_SERVER_HOSTNAME}/{path}");
|
||||
println!("Downloading from {url}");
|
||||
let url = format!("https://{}/{}", UPDATE_SERVER_HOSTNAME, path);
|
||||
|
||||
println!("Downloading from {}", url);
|
||||
|
||||
let component_download_response = client.get(&url).send()?;
|
||||
|
||||
@@ -284,10 +319,10 @@ fn download_file(path: &str) -> Result<std::path::PathBuf> {
|
||||
|
||||
let mut content = std::io::Cursor::new(component_download_response.bytes()?);
|
||||
let mut file =
|
||||
std::fs::File::create(&path).context(format!("Failed to create file at {path}"))?;
|
||||
std::fs::File::create(&path).context(format!("Failed to create file at {}", path))?;
|
||||
std::io::copy(&mut content, &mut file)?;
|
||||
|
||||
println!("Saved file to {path}");
|
||||
println!("Saved file to {}", path);
|
||||
|
||||
Ok(std::path::PathBuf::from(path))
|
||||
}
|
||||
|
@@ -1,16 +1,10 @@
|
||||
use clap::{arg, command, ArgAction, Command};
|
||||
use std::process::exit;
|
||||
use libcitadel::util;
|
||||
|
||||
mod fetch;
|
||||
|
||||
pub fn main() {
|
||||
if !util::is_euid_root() {
|
||||
println!("Please run this program as root");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let matches = command!()
|
||||
let matches = command!() // requires `cargo` feature
|
||||
.subcommand_required(true)
|
||||
.subcommand(Command::new("check").about("Check for updates from remote server"))
|
||||
.subcommand(
|
||||
@@ -52,4 +46,4 @@ pub fn main() {
|
||||
println!("Error: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -63,7 +63,6 @@ fn dispatch_command(args: Vec<String>) {
|
||||
"image" => image::main(),
|
||||
"realmfs" => realmfs::main(),
|
||||
"update" => update::main(rebuild_args("citadel-update", args)),
|
||||
"fetch" => update::main(rebuild_args("citadel-fetch", args)),
|
||||
"mkimage" => mkimage::main(rebuild_args("citadel-mkimage", args)),
|
||||
"sync" => sync::main(rebuild_args("citadel-desktop-sync", args)),
|
||||
"run" => do_citadel_run(rebuild_args("citadel-run", args)),
|
||||
|
@@ -38,7 +38,7 @@ impl UpdateBuilder {
|
||||
}
|
||||
|
||||
fn target_filename(&self) -> String {
|
||||
format!("citadel-{}-{}-{}.img", self.config.img_name(), self.config.channel(), self.config.version())
|
||||
format!("citadel-{}-{}-{:}.img", self.config.img_name(), self.config.channel(), self.config.version())
|
||||
}
|
||||
|
||||
fn build_filename(config: &BuildConfig) -> String {
|
||||
|
@@ -21,6 +21,7 @@ dbus = "0.6"
|
||||
posix-acl = "1.0.0"
|
||||
procfs = "0.12.0"
|
||||
semver = "1.0"
|
||||
anyhow = "1.0"
|
||||
clap = "4.5"
|
||||
|
||||
[dependencies.inotify]
|
||||
|
@@ -84,8 +84,8 @@ impl OsRelease {
|
||||
OsRelease::get_value("CITADEL_IMAGE_PUBKEY")
|
||||
}
|
||||
|
||||
pub fn citadel_rootfs_version() -> Option<&'static str> {
|
||||
OsRelease::get_value("CITADEL_ROOTFS_VERSION")
|
||||
pub fn citadel_rootfs_version() -> Option<usize> {
|
||||
OsRelease::get_int_value("CITADEL_ROOTFS_VERSION")
|
||||
}
|
||||
|
||||
pub fn citadel_kernel_version() -> Option<&'static str> {
|
||||
@@ -96,14 +96,6 @@ impl OsRelease {
|
||||
OsRelease::get_value("CITADEL_KERNEL_ID")
|
||||
}
|
||||
|
||||
pub fn citadel_client() -> Option<&'static str> {
|
||||
OsRelease::get_value("CITADEL_CLIENT")
|
||||
}
|
||||
|
||||
pub fn citadel_publisher() -> Option<&'static str> {
|
||||
OsRelease::get_value("CITADEL_PUBLISHER")
|
||||
}
|
||||
|
||||
fn _get_value(&self, key: &str) -> Option<&str> {
|
||||
self.vars.get(key).map(|v| v.as_str())
|
||||
}
|
||||
|
@@ -40,11 +40,6 @@ impl ResourceImage {
|
||||
pub fn find(image_type: &str) -> Result<Self> {
|
||||
let channel = Self::rootfs_channel();
|
||||
|
||||
// search when citadel is installed
|
||||
if let Some(image) = search_directory(format!("/storage/resources/{channel}/"), image_type, Some(&channel))? {
|
||||
return Ok(image);
|
||||
}
|
||||
|
||||
info!("Searching run directory for image {} with channel {}", image_type, channel);
|
||||
|
||||
if let Some(image) = search_directory(RUN_DIRECTORY, image_type, Some(&channel))? {
|
||||
@@ -358,11 +353,6 @@ impl ResourceImage {
|
||||
if Mounts::is_source_mounted("/dev/mapper/citadel-storage")? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if Mounts::is_source_mounted("/storage")? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let path = Path::new("/dev/mapper/citadel-storage");
|
||||
if !path.exists() {
|
||||
return Ok(false);
|
||||
@@ -534,14 +524,8 @@ fn maybe_add_dir_entry(entry: &DirEntry,
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if kernel_id.is_some() {
|
||||
if image_type == "kernel" && (metainfo.kernel_version() != kernel_version || metainfo.kernel_id() != kernel_id) {
|
||||
return Ok(());
|
||||
}
|
||||
} else { // in live mode, kernel_id is None
|
||||
if image_type == "kernel" && metainfo.kernel_version() != kernel_version {
|
||||
return Ok(());
|
||||
}
|
||||
if image_type == "kernel" && (metainfo.kernel_version() != kernel_version || metainfo.kernel_id() != kernel_id) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
images.push(ResourceImage::new(&path, header));
|
||||
|
@@ -15,12 +15,11 @@ impl Mounts {
|
||||
pub fn is_source_mounted<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||
let path = path.as_ref();
|
||||
|
||||
for i in Self::load()?.mounts() {
|
||||
if i.line.contains(&path.display().to_string()) {
|
||||
return Ok(true)
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
let mounted = Self::load()?
|
||||
.mounts()
|
||||
.any(|m| m.source_path() == path);
|
||||
|
||||
Ok(mounted)
|
||||
}
|
||||
|
||||
pub fn is_target_mounted<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||
|
@@ -11,7 +11,7 @@ pub struct CryptoContainerFile {
|
||||
pub signatory: String, // name of org or person who holds the key
|
||||
}
|
||||
|
||||
/// This struct contains the entirety of the information needed to decide whether to update or not
|
||||
/// This struct contains the entirety of the logical information needed to decide whether to update or not
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CitadelVersionStruct {
|
||||
pub client: String,
|
||||
@@ -65,7 +65,7 @@ impl std::fmt::Display for AvailableComponentVersion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} image has version {}",
|
||||
"({} image has version: {})",
|
||||
self.component, self.version
|
||||
)
|
||||
}
|
||||
|
21
update-generator/Cargo.toml
Normal file
21
update-generator/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "update_generator"
|
||||
description = "Utility to create updates for citadel's components"
|
||||
version = "0.1.0"
|
||||
authors = ["isa <isa@subgraph.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
libcitadel = { path = "../libcitadel" }
|
||||
ed25519-dalek = {version = "2.1", features = ["rand_core", "pem"]}
|
||||
serde_cbor = "0.11"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_derive = "1.0"
|
||||
rand = "0.8"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
rpassword = "7.3"
|
||||
semver = "1.0"
|
||||
zeroize = "1.8"
|
||||
sodiumoxide = "0.2"
|
||||
anyhow = "1.0"
|
||||
glob = "0.3"
|
63
update-generator/README.md
Normal file
63
update-generator/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Introduction
|
||||
|
||||
This code is used to produce the files needed to update citadel.
|
||||
The citadel-fetch command is installed in citadel and may be run manually to check for updates.
|
||||
|
||||
N.B.
|
||||
This update framework is at the moment not production ready. It defends against arbitrary installation and rollback attacks. It partially defends against endless data attacks.
|
||||
It does not defend against fast-forward or indefinite freeze attacks. This system is vulnerable to key compromise and does not provide a method to update the root key through the update mechanism.
|
||||
Further work needs to be done to prevent these issues. Consider using libraries from the Update Framework (TUF).
|
||||
|
||||
# Usage
|
||||
|
||||
Within the citadel-tools directory, run `cargo run --bin update_generator generate-keypair` to generate a keypair. Replace the existing public key file by moving update_server_key.pub to citadel/meta-citadel/recipes-citadel/citadel-config/files/citadel-fetch/update_server_key.pub .
|
||||
The private key is available in the same directory as keypair.priv.
|
||||
|
||||
Run `cargo run --bin update_generator create-signed-file -r 1.0.0 -k 6.5.3 -e 1.0.0` replacing the version numbers with yours and a version.cbor file will be generated.
|
||||
Alternatively, the `create-signed-file` can be run with no parameters in which case it will read the version number of the available images in build/images and generate a version.cbor automagically.
|
||||
|
||||
Finally, the files generated (version.cbor and images) must be uploaded/ Run `cargo run --bin update_generator upload-to-server` to automatically upload all the files to the server.
|
||||
To perform the upload manually, put this version.cbor file to the server at: {UPDATE_SERVER_NAME}/{CLIENT}/{CHANNEL}/. For example: https://update.subgraph.com/public/dev/ .
|
||||
Upload the image files to {UPDATE_SERVER_NAME}/{CLIENT}/{CHANNEL}/. For example: https://update.subgraph.com/public/dev/rootfs_1.0.0.img
|
||||
|
||||
More information is available in the `update_generator` help menu.
|
||||
|
||||
For debugging, testing and internal dev use, the config.toml file provides a way to change the path on the server where the files are saved by changing the CLIENT and CHANNEL.
|
||||
|
||||
# Basic structure
|
||||
|
||||
We host the files used by this program as a file server. The current hostname is update.subgraph.com.
|
||||
|
||||
An installed citadel server will automatically check for updates by reading the file at https://update.subgraph.com/{CLIENT}/{CHANNEL}/version.cbor
|
||||
|
||||
{CLIENT} will default to "public" but is read from the currently running system.
|
||||
|
||||
{CHANNEL} will default to "prod" but is read from the currently running system.
|
||||
|
||||
The version.cbor file is signed with Subgraph-controlled keys or equivalent client-controlled keys. The corresponding public key will be embedded in the rootfs image during build. The public key is called update_server_key.pub and is stored in /etc/citadel/ .
|
||||
|
||||
# Update File Structure
|
||||
|
||||
## version.cbor file
|
||||
|
||||
The version.cbor file is the only file read during update and contains all information required for the user's system to decide to update or not as well as where the update files are located on the server.
|
||||
This file is merely a container which provides a cryptographic guarantee that the serialized_citadel_version struct (which contains the actual information we need to disseminate) is authentic.
|
||||
|
||||
1. serialized_citadel_version (CitadelVersionStruct): the serialized data containing the actionable information we need to read to make decisions on the update
|
||||
2. signature: the ed25519 signature of the above serialized data
|
||||
3. signatory: the name of the org or person who produced the version.cbor file
|
||||
|
||||
|
||||
## CitadelVersionStruct
|
||||
|
||||
1. client: the Subgraph client making the request
|
||||
2. channel: whether this is a production release or other
|
||||
3. component_version (Vec<ComponentVersion>): a vector containing structures called ComponentVersion which contain the information on each component's version number and the location of the download file of the component
|
||||
4. publisher: the name of the org or person who released this update
|
||||
|
||||
|
||||
## ComponentVersion
|
||||
|
||||
1. component: the name of the image we may update. Either the rootfs, the kernel or the extra image
|
||||
2. version: a string which contains the semver describing the version of the component
|
||||
3. file_path: where on the update server can the file be downloaded from. This is relative to the domain name we are currently fetching from
|
@@ -36,7 +36,7 @@ const LAST_RESORT_CLIENT: &str = "public";
|
||||
const LAST_RESORT_CHANNEL: &str = "prod";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = "Perform tasks needed to create an update and publish it")]
|
||||
#[command(about = "Generate the file we use to inform clients of updates")]
|
||||
#[command(author, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
@@ -424,6 +424,7 @@ fn verify_version_signature(
|
||||
// Make sure to add your ssh key to the "updates" user on the server
|
||||
fn send_with_scp(from: &PathBuf, to: &PathBuf) -> Result<()> {
|
||||
Command::new("scp")
|
||||
.arg("-P 22514")
|
||||
.arg(from)
|
||||
.arg(format!(
|
||||
"updates@{}:/updates/files/{}",
|
||||
|
Reference in New Issue
Block a user