Compare commits
	
		
			No commits in common. "f9781c659b8a388503b2e614c084a9f480746b7d" and "11a5b122c82215bf524f8a94aac1d68a1d56cbed" have entirely different histories.
		
	
	
		
			f9781c659b
			...
			11a5b122c8
		
	
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,3 @@ | ||||
| dtrfs.tar | ||||
| build | ||||
| tmp | ||||
| target | ||||
|  | ||||
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
								
							| @ -1,37 +1,17 @@ | ||||
| ## OS template | ||||
| 
 | ||||
| You will need a working OS template to work with this project. | ||||
| 
 | ||||
| Easy solution create an OS template: | ||||
| - start any archlinux machine (the arch installer also works) | ||||
| - install `arch-install-scripts` | ||||
| - run `pacstrap /mnt base openssh` to install base packages to /mnt | ||||
| - mount the archlinux installation .iso in a VM | ||||
| - run `pacstrap /mnt base linux openssh` | ||||
| - run `ln -s /usr/lib/systemd/system/sshd.service /mnt/etc/systemd/system/multi-user.target.wants/sshd.service` | ||||
| - run `fsarchiver savedir /tmp/os_template.fsa /mnt` to save your OS template | ||||
| - download `/tmp/os_template.fsa` to your machine | ||||
| - run `fsarchiver savedir /tmp/os_template.fsa /mnt` | ||||
| - download `/tmp/os_template.fsa` | ||||
| - upload the `os_template.fsa` anywhere so that it can be downloaded with wget | ||||
| 
 | ||||
| Some notes on the above: | ||||
| - base is the only package required to run a dtrfs VM; the kernel is not needed cause we are using SNP | ||||
| - base and linux are the only packages to run a VM | ||||
| - you will need sshd to operate the VM, so create the symlink to make it start with the OS | ||||
| - fsarchiver is very good at preserving OS data | ||||
| - fsarchiver saves the absolute path (which means you must use `/mnt` as this is hardcoded) | ||||
| - the initrd will dump that template to the encrypted disk | ||||
| - the same procedure can be used with any distribution, but we didn't test that yet | ||||
| 
 | ||||
| ## initrd and linux | ||||
| 
 | ||||
| You will need an initrd and a kernel to run SNP VMs. | ||||
| 
 | ||||
| - start any archlinux machine | ||||
| - clone this repo | ||||
| - inspect your kernel version by running `file -sL /boot/vmlinuz-linux`. | ||||
| - (optional) update the kernel version in `./creator_exports.sh` | ||||
| - create the initrd by running `./create.sh`; this will save the initrd in the build folder | ||||
| - grab your kernel from `/boot/vmlinuz-linux` and... | ||||
| - ... upload kernel and initrd to your hypervizor | ||||
| 
 | ||||
| ## module scanner | ||||
| 
 | ||||
| Optionally, you can use `./remote_create.sh` to upload this repo to remote node and build your initrd. | ||||
| This will automatically scan the kernel modules running on the remote host, and package all modules in the initrd. This is ideal if your VM has a setup that is not cover by the modules hardcoded in this repo. | ||||
|  | ||||
| @ -5,9 +5,6 @@ source creator_functions.sh | ||||
| mkdir -p build | ||||
| cd build | ||||
| 
 | ||||
| echo_cyan "Installing build dependencies..." | ||||
| install_build_deps | ||||
| 
 | ||||
| echo_cyan "Starting installation at $ROOT." | ||||
| create_dirs | ||||
| 
 | ||||
| @ -23,25 +20,27 @@ install_binary $(which mkfs.ext4) | ||||
| install_binary $(which fsarchiver) | ||||
| install_kmod | ||||
| install_busybox | ||||
| install_dtrfs_api | ||||
| install_guest_api | ||||
| 
 | ||||
| echo_cyan "Installing scripts..." | ||||
| install_init_script | ||||
| 
 | ||||
| echo_cyan "Installing base modules required to boot" | ||||
| echo_cyan "Installing kernel modules..." | ||||
| # # Uncomment this section if you want to grab modules from the guest OS | ||||
| # scan_modules | ||||
| install_module virtio_net | ||||
| install_module ext4 | ||||
| install_module virtio_blk | ||||
| install_module msr | ||||
| install_module sev-guest | ||||
| install_module dm_crypt | ||||
| install_module hid-generic | ||||
| install_module dm-integrity | ||||
| 
 | ||||
| install_guest_mods | ||||
| 
 | ||||
| [[ "$GRAB_LOCAL_MODS" == "YES" ]] && { | ||||
|   scan_modules | ||||
|   backup_active_modules | ||||
| } | ||||
| install_module cbc | ||||
| install_module hmac | ||||
| install_module sha256 | ||||
| install_module rng | ||||
| install_module aes | ||||
| 
 | ||||
| echo_cyan "Building module dependency tree..." | ||||
| cp /lib/modules/${KERNEL}/modules.{order,builtin,builtin.modinfo} "${ROOT}/lib/modules/${KERNEL}/" | ||||
							
								
								
									
										7
									
								
								creator_exports.sh
									
									
									
									
									
										Executable file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										7
									
								
								creator_exports.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | ||||
| #!/bin/bash | ||||
| script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) | ||||
| 
 | ||||
| # root of the initrd, that will be used to create the cpio archive | ||||
| export ROOT="${script_dir}/build/initrd_root" | ||||
| export BUSYBOX_PATH="/usr/lib/initcpio/busybox" | ||||
| export KERNEL="$(uname -r)" | ||||
| @ -16,24 +16,11 @@ echo_red() { | ||||
|   echo -e "\033[0;31m$1\033[0m" | ||||
| } | ||||
| 
 | ||||
| # TODO: prepare DTRFS to create initrd for other distros | ||||
| install_build_deps() { | ||||
|   if grep "Arch Linux" /etc/os-release > /dev/null; then | ||||
|     which wget fsarchiver cpio mkinitcpio || { | ||||
|       echo_red "Please install build deps: wget fsarchiver cpio mkinitcpio" | ||||
|       exit 1 | ||||
|     } | ||||
|     qemu_guest_modules="../arch_guest_mods" | ||||
|   else | ||||
|     echo_red "ArchLinux is the only distribution currently supported by DTRFS." | ||||
|     exit 1 | ||||
|   fi | ||||
| } | ||||
| 
 | ||||
| create_dirs() { | ||||
|   rm -rf "$ROOT" 2>/dev/null | ||||
|   mkdir -p "${ROOT}/usr/bin/" | ||||
|   mkdir -p "${ROOT}/usr/bin" | ||||
|   mkdir -p "${ROOT}/usr/lib/dtrfs" | ||||
|   mkdir -p "${ROOT}/usr/lib" | ||||
|   mkdir -p "${ROOT}/dev" | ||||
|   mkdir -p "${ROOT}/etc" | ||||
|   mkdir -p "${ROOT}/mnt" | ||||
| @ -46,9 +33,9 @@ create_dirs() { | ||||
|   ln -s usr/bin "${ROOT}/sbin" | ||||
|   ln -s usr/lib "${ROOT}/lib" | ||||
|   ln -s usr/lib "${ROOT}/lib64" | ||||
|   ln -s lib     "${ROOT}/usr/lib64" | ||||
|   ln -s bin     "${ROOT}/usr/sbin" | ||||
|   ln -s ../run  "${ROOT}/var/run" | ||||
|   ln -s lib "${ROOT}/usr/lib64" | ||||
|   ln -s bin "${ROOT}/usr/sbin" | ||||
|   ln -s ../run "${ROOT}/var/run" | ||||
| } | ||||
| 
 | ||||
| # Installs a library. Expects absolute path. | ||||
| @ -115,8 +102,7 @@ install_busybox() { | ||||
| 
 | ||||
| install_init_script() { | ||||
|   cp ../init.sh "${ROOT}/init" | ||||
|   cp ../init_functions.sh "${ROOT}/usr/lib/dtrfs/" | ||||
|   cp ../install_os.sh "${ROOT}/usr/lib/dtrfs/" | ||||
|   cp ../init_functions.sh "${ROOT}/" | ||||
| } | ||||
| 
 | ||||
| install_module() { | ||||
| @ -149,28 +135,8 @@ _install_module() { | ||||
|   done <<< "$( echo "$depends" )" | ||||
| } | ||||
| 
 | ||||
| install_guest_mods() { | ||||
|   local mod='' | ||||
|   echo_cyan "Installing kernel modules needed for QEMU guests..." | ||||
|   while read -r mod; do | ||||
|     [[ -z $mod ]] && continue | ||||
|     _install_module "$mod" | ||||
|   done <<< "$(cat "$qemu_guest_modules")" | ||||
| } | ||||
| 
 | ||||
| backup_active_modules() { | ||||
|   local modules='' mod='' | ||||
|   echo_yellow "Installing to the initrd all kernel modules currently loaded..." | ||||
|   modules="$(lsmod | awk '{ print $1 }' | grep -v Module)" | ||||
|   while read -r mod; do | ||||
|     [[ -z $mod ]] && continue | ||||
|     _install_module "$mod" | ||||
|   done <<< "$( echo "$modules" )" | ||||
| } | ||||
| 
 | ||||
| scan_modules() { | ||||
|   local drivers='' mod='' | ||||
|   echo_yellow "Installing kernel modules based on current hardware..." | ||||
|   install_module "$(df -T / | awk '{ print $2 }' | tail -1)" | ||||
| 
 | ||||
|   drivers=$(lshw -c disk 2>/dev/null | grep -oE 'driver=[a-z\_\-]+' | cut -d '=' -f2; | ||||
| @ -182,24 +148,29 @@ scan_modules() { | ||||
|   done <<< "$( echo "$drivers" )" | ||||
| } | ||||
| 
 | ||||
| install_dtrfs_api() { | ||||
|   local my_location="$(pwd)" | ||||
|   cd ../../dtrfs_api && cargo build --release || { | ||||
|     echo_yellow "Could not build dtrfs_api. Looking for binary at $(pwd)/dtrfs_api" | ||||
|   } | ||||
|   cd "$my_location" | ||||
|   cp ../../dtrfs_api/target/release/dtrfs_api ./ | ||||
|   install_binary "$(pwd)/dtrfs_api" | ||||
| install_guest_api() { | ||||
|   my_location="$(pwd)" | ||||
|   echo_blue "Building guest_api with cargo and saving log to ${my_location}/guest_api.log" | ||||
|   git clone git@gitea.detee.cloud:SNP/remote_decryption.git | ||||
|   cd remote_decryption/guest_api | ||||
|   # TODO: stick to master branch after code stabilizes | ||||
|   git checkout dtrfs | ||||
|   git pull | ||||
|   cargo build --release > "${my_location}/guest_api.log" 2>&1 || | ||||
|     echo_red "Failed to build guest_api" | ||||
|   strip --discard-all target/release/guest_api | ||||
|   install_binary "$(pwd)/target/release/guest_api" | ||||
|   cd $my_location | ||||
| } | ||||
| 
 | ||||
| create_archive() { | ||||
|   local archive="detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz" | ||||
|   echo_cyan "Creating archive build/$archive" | ||||
|   echo_cyan "Creating archive $archive" | ||||
|   echo $archive > .archive_name | ||||
|   my_location="$(pwd)" | ||||
|   cd ${ROOT} | ||||
|   find . | cpio -o -H newc | gzip \ | ||||
|     > "${my_location}/detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz" | ||||
| 
 | ||||
|   cd "$my_location" | ||||
|   cd $my_location | ||||
| } | ||||
							
								
								
									
										2537
									
								
								dtrfs_api/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2537
									
								
								dtrfs_api/Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,18 +0,0 @@ | ||||
| [package] | ||||
| name = "dtrfs_api" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [dependencies] | ||||
| anyhow = "1.0.93" | ||||
| base64 = "0.22.1" | ||||
| bincode = "1.3.3" | ||||
| regex = "1.11.1" | ||||
| sev = { version = "4.0", default-features = false, features = ['crypto_nossl','snp'] } | ||||
| ed25519-dalek = { version = "2.1.1", features = ["pem", "pkcs8"] } | ||||
| lazy_static = "1.5.0" | ||||
| actix-web = { version = "4.9.0", features = ["rustls-0_23"] } | ||||
| sha3 = "0.10.8" | ||||
| rustls = "0.23.18" | ||||
| rustls-pemfile = "2.2.0" | ||||
| serde = { version = "1.0.215", features = ["derive"] } | ||||
| @ -1,3 +0,0 @@ | ||||
| reorder_impl_items = true | ||||
| use_small_heuristics = "Max" | ||||
| merge_imports = true | ||||
| @ -1,205 +0,0 @@ | ||||
| mod os; | ||||
| mod snp; | ||||
| 
 | ||||
| use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer}; | ||||
| use base64::prelude::{Engine, BASE64_URL_SAFE}; | ||||
| use ed25519_dalek::{pkcs8::DecodePublicKey, Signature, Verifier, VerifyingKey}; | ||||
| use lazy_static::lazy_static; | ||||
| use regex::Regex; | ||||
| use rustls::{pki_types::PrivateKeyDer, ServerConfig}; | ||||
| use rustls_pemfile::{certs, pkcs8_private_keys}; | ||||
| use serde::Deserialize; | ||||
| use sha3::{Digest, Sha3_512}; | ||||
| use std::{ | ||||
|     fs::File, | ||||
|     io::{BufReader, Read}, | ||||
| }; | ||||
| 
 | ||||
| const CRT_FILE: &str = "/tmp/certs/dtrfs_api.crt"; | ||||
| const KEY_FILE: &str = "/tmp/certs/dtrfs_api.key"; | ||||
| const CMDLINE_FILE: &str = "/proc/cmdline"; | ||||
| 
 | ||||
| lazy_static! { | ||||
|     static ref SNP_REPORT: String = snp::get_report_as_base64(get_cert_hash()).unwrap(); | ||||
|     static ref CRT_CONTENTS: String = { | ||||
|         let mut msg = String::new(); | ||||
|         let _ = BufReader::new(File::open(CRT_FILE).unwrap()).read_to_string(&mut msg); | ||||
|         msg | ||||
|     }; | ||||
|     static ref CMDLINE: String = { | ||||
|         let mut cmdline = String::new(); | ||||
|         let _ = BufReader::new(File::open(CMDLINE_FILE).unwrap()).read_to_string(&mut cmdline); | ||||
|         cmdline | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn get_cert_hash() -> [u8; 64] { | ||||
|     let mut hasher = Sha3_512::new(); | ||||
|     let crt = File::open(CRT_FILE).expect("Could not open crt file."); | ||||
|     let mut buf_reader = BufReader::new(crt); | ||||
|     let mut buffer = Vec::new(); | ||||
|     buf_reader.read_to_end(&mut buffer).expect("Could not read certificate."); | ||||
|     hasher.update(buffer); | ||||
|     let crt_hash = hasher.finalize(); | ||||
|     crt_hash.into() | ||||
| } | ||||
| 
 | ||||
| fn verifying_key() -> Result<VerifyingKey, Box<dyn std::error::Error>> { | ||||
|     let re = Regex::new(r"detee_admin=([A-Za-z0-9+/=]+)").unwrap(); | ||||
|     let key_str = re.find(&CMDLINE).map(|m| m.as_str()).unwrap_or(""); | ||||
|     let key_pem = format!( | ||||
|         "-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n", | ||||
|         key_str.strip_prefix("detee_admin=").ok_or("Could not get admin key from cmdline")? | ||||
|     ); | ||||
|     Ok(VerifyingKey::from_public_key_pem(&key_pem)?) | ||||
| } | ||||
| 
 | ||||
| fn verify(req: &HttpRequest) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let signature = req | ||||
|         .headers() | ||||
|         .get("ed25519-signature") | ||||
|         .ok_or_else(|| "Did not find ed25519-signature header")?; | ||||
| 
 | ||||
|     let signature: &[u8] = &BASE64_URL_SAFE.decode(signature)?; | ||||
|     let signature = Signature::from_bytes(signature.try_into()?); | ||||
|     let verifying_key = verifying_key()?; | ||||
|     Ok(verifying_key.verify(CRT_CONTENTS.as_bytes(), &signature)?) | ||||
| } | ||||
| 
 | ||||
| #[get("/")] | ||||
| async fn homepage() -> HttpResponse { | ||||
|     let mut text: String = "Available commands:\n".to_string(); | ||||
|     text += "GET: /report /ssh_key\n"; | ||||
|     text += "POST: /install /decrypt /switch_root /ssh_key\n"; | ||||
|     text += "\nAll requests require the ed25519-signature header"; | ||||
|     HttpResponse::Ok().body(text) | ||||
| } | ||||
| 
 | ||||
| #[get("/report")] | ||||
| async fn get_report() -> HttpResponse { | ||||
|     HttpResponse::Ok().body(SNP_REPORT.clone()) | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct InstallForm { | ||||
|     url: String, | ||||
|     sha: String, | ||||
|     keyfile: String, | ||||
| } | ||||
| 
 | ||||
| // TODO: QA this function to make sure we don't accidentally allow empty string keyfile
 | ||||
| #[post("/install")] | ||||
| async fn post_install_form(req: HttpRequest, form: web::Form<InstallForm>) -> HttpResponse { | ||||
|     if let Err(e) = verify(&req) { | ||||
|         return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e)); | ||||
|     }; | ||||
|     match os::encrypt_and_install_os(&form.url, &form.sha, &form.keyfile) { | ||||
|         Ok(s) => HttpResponse::Ok().body(s), | ||||
|         Err(e) => HttpResponse::InternalServerError().body(format!("{e:?}")), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct DecryptForm { | ||||
|     keyfile: String, | ||||
| } | ||||
| 
 | ||||
| // TODO: QA this function to make sure we don't accidentally allow empty string keyfile
 | ||||
| #[post("/decrypt")] | ||||
| async fn post_decrypt_form(req: HttpRequest, form: web::Form<DecryptForm>) -> HttpResponse { | ||||
|     if let Err(e) = verify(&req) { | ||||
|         return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e)); | ||||
|     }; | ||||
|     let decrypt_result = os::try_backup_keyfile(&form.keyfile); | ||||
|     if let Err(decryption_error) = decrypt_result { | ||||
|         return HttpResponse::BadRequest() | ||||
|             .body(format!("Could not decrypt root: {decryption_error:?}")); | ||||
|     } | ||||
|     let hot_key_result = os::replace_hot_keyfile(); | ||||
|     HttpResponse::Ok().body(format!("{:?}\n{:?}", decrypt_result, hot_key_result)) | ||||
| } | ||||
| 
 | ||||
| #[post("/switch_root")] | ||||
| async fn post_process_exit(req: HttpRequest) -> HttpResponse { | ||||
|     if let Err(e) = verify(&req) { | ||||
|         return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e)); | ||||
|     }; | ||||
|     std::process::exit(0); | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct SSHKeyForm { | ||||
|     ssh_key: String, | ||||
| } | ||||
| 
 | ||||
| #[get("/ssh_key")] | ||||
| async fn get_ssh_keys(req: HttpRequest) -> HttpResponse { | ||||
|     if let Err(e) = verify(&req) { | ||||
|         return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e)); | ||||
|     }; | ||||
|     match os::list_ssh_keys() { | ||||
|         Ok(keys) => HttpResponse::Ok().body(keys), | ||||
|         Err(e) => HttpResponse::BadRequest().body(format!("{e:?}")), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[post("/ssh_key")] | ||||
| async fn post_ssh_key(req: HttpRequest, form: web::Form<SSHKeyForm>) -> HttpResponse { | ||||
|     if let Err(e) = verify(&req) { | ||||
|         return HttpResponse::BadRequest().body(format!("Signature verification failed: {}", e)); | ||||
|     }; | ||||
|     let ssh_key = &form.ssh_key; | ||||
|     match os::add_ssh_key(ssh_key) { | ||||
|         Ok(()) => HttpResponse::Ok().body("Key added to authorized_keys"), | ||||
|         Err(e) => HttpResponse::BadRequest().body(format!("{e:?}")), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn load_rustls_config() -> rustls::ServerConfig { | ||||
|     rustls::crypto::aws_lc_rs::default_provider().install_default().unwrap(); | ||||
| 
 | ||||
|     let config = ServerConfig::builder().with_no_client_auth(); | ||||
| 
 | ||||
|     let cert_file = &mut BufReader::new(File::open(CRT_FILE).unwrap()); | ||||
|     let key_file = &mut BufReader::new(File::open(KEY_FILE).unwrap()); | ||||
| 
 | ||||
|     let cert_chain = certs(cert_file).collect::<Result<Vec<_>, _>>().unwrap(); | ||||
|     let mut keys = pkcs8_private_keys(key_file) | ||||
|         .map(|key| key.map(PrivateKeyDer::Pkcs8)) | ||||
|         .collect::<Result<Vec<_>, _>>() | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     if keys.is_empty() { | ||||
|         eprintln!("Could not locate PKCS 8 private keys."); | ||||
|         std::process::exit(1); | ||||
|     } | ||||
| 
 | ||||
|     config.with_single_cert(cert_chain, keys.remove(0)).unwrap() | ||||
| } | ||||
| 
 | ||||
| #[actix_web::main] | ||||
| async fn main() -> std::io::Result<()> { | ||||
|     match os::try_hot_keyfile() { | ||||
|         Ok(_) => { | ||||
|             println!("Hot decryption successful. Booting OS..."); | ||||
|             return Ok(()); | ||||
|         }, | ||||
|         Err(e) => { | ||||
|             println!("Hot decryption failed: {e:?}"); | ||||
|         } | ||||
|     } | ||||
|     let config = load_rustls_config(); | ||||
|     HttpServer::new(|| { | ||||
|         App::new() | ||||
|             .service(post_install_form) | ||||
|             .service(post_decrypt_form) | ||||
|             .service(post_process_exit) | ||||
|             .service(post_ssh_key) | ||||
|             .service(get_ssh_keys) | ||||
|             .service(get_report) | ||||
|             .service(homepage) | ||||
|     }) | ||||
|     .bind_rustls_0_23("[::]:8443", config)? | ||||
|     .run() | ||||
|     .await | ||||
| } | ||||
| @ -1,147 +0,0 @@ | ||||
| use crate::snp::get_derived_key; | ||||
| use anyhow::{anyhow, Result}; | ||||
| use base64::prelude::{Engine, BASE64_URL_SAFE}; | ||||
| use std::{ | ||||
|     fs::File, | ||||
|     io::{BufRead, BufReader, Write}, | ||||
|     path::Path, | ||||
|     process::Command, | ||||
| }; | ||||
| 
 | ||||
| const SNP_KEYFILE_PATH: &str = "/tmp/detee_snp_keyfile"; | ||||
| const BACKUP_KEYFILE_PATH: &str = "/tmp/detee_backup_keyfile"; | ||||
| 
 | ||||
| pub fn encrypt_and_install_os( | ||||
|     install_url: &str, | ||||
|     install_sha: &str, | ||||
|     keyfile: &str, | ||||
| ) -> Result<String> { | ||||
|     let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?; | ||||
|     std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?; | ||||
|     // this path is hardcoded also in the initrd creation script
 | ||||
|     let install_result = Command::new("/usr/lib/dtrfs/install_os.sh") | ||||
|         .env("INSTALL_URL", install_url) | ||||
|         .env("INSTALL_SHA", install_sha) | ||||
|         .env("SNP_KEY_FILE", SNP_KEYFILE_PATH) | ||||
|         .env("ROOT_KEYFILE", BACKUP_KEYFILE_PATH) | ||||
|         .output()?; | ||||
| 
 | ||||
|     if !install_result.status.success() { | ||||
|         return Err(anyhow!( | ||||
|             "OS installation script failed.\nScript stdout:\n{}\nScript stderr:\n{}", | ||||
|             String::from_utf8(install_result.stdout) | ||||
|                 .unwrap_or("Could not grab stdout from installation script.".to_string()), | ||||
|             String::from_utf8(install_result.stderr) | ||||
|                 .unwrap_or("Could not grab stderr from installation script.".to_string()), | ||||
|         )); | ||||
|     } | ||||
|     Ok(format!( | ||||
|         "Successfully installed OS. Script stdout:\n{}\nScript stderr:\n{}", | ||||
|         String::from_utf8(install_result.stdout) | ||||
|             .unwrap_or("Could not grab stdout from installation script.".to_string()), | ||||
|         String::from_utf8(install_result.stderr) | ||||
|             .unwrap_or("Could not grab stderr from installation script.".to_string()), | ||||
|     )) | ||||
| } | ||||
| 
 | ||||
| pub fn try_hot_keyfile() -> Result<()> { | ||||
|     let hot_key = get_derived_key()?; | ||||
|     std::fs::write(SNP_KEYFILE_PATH, hot_key)?; | ||||
|     decrypt_and_mount(SNP_KEYFILE_PATH)?; | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| pub fn try_backup_keyfile(keyfile: &str) -> Result<String> { | ||||
|     let binary_keyfile = BASE64_URL_SAFE.decode(keyfile)?; | ||||
|     std::fs::write(BACKUP_KEYFILE_PATH, binary_keyfile)?; | ||||
|     decrypt_and_mount(BACKUP_KEYFILE_PATH)?; | ||||
|     Ok("Succesfully mounted /mnt using backup keyfile.".to_string()) | ||||
| } | ||||
| 
 | ||||
| fn decrypt_and_mount(keyfile_path: &str) -> Result<()> { | ||||
|     let decryption_result = Command::new("cryptsetup") | ||||
|         .arg("open") | ||||
|         .arg("--key-file") | ||||
|         .arg(keyfile_path) | ||||
|         .arg("/dev/vda1") | ||||
|         .arg("root") | ||||
|         .output()?; | ||||
|     if !decryption_result.status.success() { | ||||
|         return Err(anyhow!("Could not decrypt disk.")); | ||||
|     } | ||||
|     let mount_result = Command::new("mount").arg("/dev/mapper/root").arg("/mnt").output()?; | ||||
|     if !mount_result.status.success() { | ||||
|         return Err(anyhow!("Could not mount /dev/mapper/root to /mnt")); | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| pub fn replace_hot_keyfile() -> Result<String> { | ||||
|     let _delete_old_keyfile = Command::new("cryptsetup") | ||||
|         .arg("luksKillSlot") | ||||
|         .arg("-d") | ||||
|         .arg(BACKUP_KEYFILE_PATH) | ||||
|         .arg("/dev/vda1") | ||||
|         .arg("1") | ||||
|         .output(); | ||||
| 
 | ||||
|     let meta = std::fs::metadata(SNP_KEYFILE_PATH)?; | ||||
|     if meta.len() == 0 { | ||||
|         return Err(anyhow!("Could not replace hot keyfile using SNP KDF.")); | ||||
|     } | ||||
| 
 | ||||
|     let _add_hot_keyfile = Command::new("cryptsetup") | ||||
|         .arg("luksAddKey") | ||||
|         .arg("--key-file") | ||||
|         .arg(BACKUP_KEYFILE_PATH) | ||||
|         .arg("--new-keyfile") | ||||
|         .arg(SNP_KEYFILE_PATH) | ||||
|         .arg("/dev/vda1") | ||||
|         .output(); | ||||
| 
 | ||||
|     Ok("Succesfully replaced hot keyfile using SNP KDF.".to_string()) | ||||
| } | ||||
| 
 | ||||
| pub fn add_ssh_key(key: &str) -> Result<()> { | ||||
|     use std::os::unix::fs::PermissionsExt; | ||||
|     if !Path::new("/mnt/etc/os-release").try_exists().is_ok_and(|found| found == true) { | ||||
|         return Err(anyhow!( | ||||
|             "Operating system not mounted. Please install OS or decrypt existing OS." | ||||
|         )); | ||||
|     } | ||||
| 
 | ||||
|     let encoded_key: Vec<&str> = key.split(" ").collect(); | ||||
|     if encoded_key.len() < 2 { | ||||
|         return Err(anyhow!("Supplied key is expected to have at least two words.")); | ||||
|     } | ||||
|     if let Ok(keys) = File::open("/mnt/.ssh/authorized_keys") { | ||||
|         let mut buffered = BufReader::new(keys).lines(); | ||||
|         while let Some(Ok(k)) = buffered.next() { | ||||
|             if k.contains(encoded_key[1]) { | ||||
|                 return Err(anyhow!("authorized_keys already contains {key}")); | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         std::fs::create_dir_all("/mnt/root/.ssh")?; | ||||
|         let permissions = std::fs::Permissions::from_mode(0o700); | ||||
|         std::fs::set_permissions("/mnt/root/.ssh", permissions)?; | ||||
|         std::fs::OpenOptions::new() | ||||
|             .create(true) | ||||
|             .write(true) | ||||
|             .open("/mnt/root/.ssh/authorized_keys")?; | ||||
|         let permissions = std::fs::Permissions::from_mode(0o600); | ||||
|         std::fs::set_permissions("/mnt/root/.ssh/authorized_keys", permissions)?; | ||||
|     } | ||||
| 
 | ||||
|     let mut keys_file = std::fs::OpenOptions::new() | ||||
|         .append(true) // Open in append mode
 | ||||
|         .create(true) // Create the file if it doesn't exist
 | ||||
|         .open("/mnt/root/.ssh/authorized_keys")?; | ||||
|     writeln!(keys_file, "{key}")?; | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| pub fn list_ssh_keys() -> Result<String> { | ||||
|     Ok(std::fs::read_to_string("/mnt/root/.ssh/authorized_keys")?) | ||||
| } | ||||
| @ -1,21 +0,0 @@ | ||||
| use anyhow::{Context, Result}; | ||||
| use sev::firmware::guest::{AttestationReport, DerivedKey, Firmware, GuestFieldSelect}; | ||||
| use base64::prelude::{Engine, BASE64_URL_SAFE}; | ||||
| 
 | ||||
| fn request_hardware_report(data: [u8; 64]) -> Result<AttestationReport> { | ||||
|     let mut fw = Firmware::open().context("unable to open /dev/sev-guest")?; | ||||
|     fw.get_report(None, Some(data), Some(0)).context("unable to fetch attestation report") | ||||
| } | ||||
| 
 | ||||
| pub fn get_report_as_base64(data: [u8; 64]) -> Result<String> { | ||||
|     let report = request_hardware_report(data)?; | ||||
|     Ok(BASE64_URL_SAFE.encode(bincode::serialize(&report)?)) | ||||
| } | ||||
| 
 | ||||
| pub fn get_derived_key() -> Result<String> { | ||||
|     let mut fw = Firmware::open()?; | ||||
|     let request = | ||||
|         DerivedKey::new(false, GuestFieldSelect(u64::from_str_radix("11111", 2)?), 1, 0, 0); | ||||
|     let derived_key: [u8; 32] = fw.get_derived_key(None, request)?; | ||||
|     Ok(BASE64_URL_SAFE.encode(derived_key)) | ||||
| } | ||||
							
								
								
									
										30
									
								
								init.sh
									
									
									
									
									
										Executable file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										30
									
								
								init.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,30 @@ | ||||
| #!/bin/bash | ||||
| source /init_functions.sh | ||||
| 
 | ||||
| export INSTALL_URL="/tmp/detee_install_url" | ||||
| export INSTALL_SHA="/tmp/detee_install_sha" | ||||
| export ROOT_KEYFILE="/tmp/detee_root_keyfile" | ||||
| export SSH_KEY_FILE="/tmp/detee_ssh_key" | ||||
| 
 | ||||
| create_mounts | ||||
| load_modules | ||||
| create_certs | ||||
| setup_network | ||||
| 
 | ||||
| # if you wait a bit, it works. The Kernel works in mysterious ways. | ||||
| sleep 5 | ||||
| modprobe sev_guest | ||||
| 
 | ||||
| guest_api || echo DeTEE API got killed by the user. | ||||
| 
 | ||||
| if [[ -f "$INSTALL_URL" ]]; then  | ||||
|   install_os | ||||
| else | ||||
|   mount_root | ||||
| fi | ||||
| 
 | ||||
| # TODO: take into consideration to remove github key injection | ||||
| github_ssh_key | ||||
| detee_ssh_key | ||||
| 
 | ||||
| exec switch_root /mnt /sbin/init "$@" | ||||
| @ -1,5 +1,13 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| echo_blue() { | ||||
|   echo -e "\033[34m$1\033[0m" | ||||
| } | ||||
| 
 | ||||
| echo_red() { | ||||
|   echo -e "\033[0;31m$1\033[0m" | ||||
| } | ||||
| 
 | ||||
| load_modules() { | ||||
|   cat /load_modules.sh | bash | ||||
| } | ||||
| @ -27,15 +35,14 @@ create_mounts() { | ||||
| 
 | ||||
| create_certs() { | ||||
|   cert_dir="/tmp/certs" | ||||
|   key="$cert_dir/dtrfs_api.key" | ||||
|   cert="$cert_dir/dtrfs_api.crt" | ||||
|   subject="/C=W3/O=DeTEE/OU=COCO/CN=dtrfs-api" | ||||
|   key="$cert_dir/guest_api.key" | ||||
|   cert="$cert_dir/guest_api.crt" | ||||
|   subject="/C=W3/O=DeTEE/OU=COCO/CN=guest-api" | ||||
|   mkdir -p "$cert_dir" | ||||
|   openssl genpkey -algorithm RSA -out "$key" \ | ||||
|     -pkeyopt rsa_keygen_bits:4096 2>/dev/null | ||||
|   openssl req -x509 -new \ | ||||
|     -key "$key" -out "$cert" \ | ||||
|     -addext "subjectAltName=DNS:dtrfs-api" \ | ||||
|     -days 365 -subj "$subject" 2>/dev/null | ||||
| } | ||||
| 
 | ||||
| @ -56,22 +63,68 @@ setup_network() { | ||||
|   ip link set eth0 up | ||||
|   ip route add default via $gateway | ||||
|   echo nameserver $nameserver > /etc/resolv.conf | ||||
|   sleep 2 | ||||
|   sleep 4 | ||||
|   ping -c 2 $gateway | ||||
| } | ||||
| 
 | ||||
| install_os() { | ||||
|   local url="$(cat $INSTALL_URL)" hostname='' | ||||
|   # mount root if it exists | ||||
|   blkid | grep vda1 | grep LUKS && { | ||||
|     mount_root | ||||
|     return 0 | ||||
|   } | ||||
|   # install OS if disk is empty | ||||
|   ( | ||||
|     echo n | ||||
|     echo p | ||||
|     echo  | ||||
|     echo  | ||||
|     echo  | ||||
|     echo w | ||||
|   ) | fdisk /dev/vda | ||||
|   cryptsetup luksFormat --batch-mode -d $ROOT_KEYFILE /dev/vda1 | ||||
|   cryptsetup open -d $ROOT_KEYFILE /dev/vda1 root | ||||
|   mkfs.ext4 /dev/mapper/root | ||||
|   mount /dev/mapper/root /mnt | ||||
|   wget -O /mnt/template.fsa "$url" | ||||
|   sha256sum /mnt/template.fsa | grep $(cat ${INSTALL_SHA}) || exit 1 | ||||
|   fsarchiver restdir /mnt/template.fsa / | ||||
|   rm /mnt/template.fsa | ||||
|   # TODO: decide for UX if maybe we should allow user to inject fstab | ||||
|   echo "" > /mnt/etc/fstab | ||||
|   hostname=$(cat /proc/cmdline | grep -oE 'detee_name=[0-9a-z\_\.\-]+' | cut -d '=' -f2) | ||||
|   [[ -n "$hostname" ]] && echo $hostname > /mnt/etc/hostname | ||||
| } | ||||
| 
 | ||||
| # detee_ghu stands for GitHub user and expects format detee_ghu=ghe0 | ||||
| github_ssh_key() { | ||||
|   local key='' | ||||
|   github_user=$(cat /proc/cmdline | grep -oE 'detee_ghu=[0-9a-zA-Z\_\.\-]+' | cut -d '=' -f2) | ||||
|   github_user=$(cat /proc/cmdline | grep -oE 'detee_ghu=[0-9a-z\_\.\-]+' | cut -d '=' -f2) | ||||
|   [[ -z "$github_user" ]] && return 0 | ||||
|   mkdir -p /mnt/root/.ssh | ||||
|   cd /mnt/root/.ssh | ||||
|   touch authorized_keys | ||||
|   key="$(wget -O - https://github.com/${github_user}.keys)" | ||||
|   grep -F "$( echo $key | awk '{ print $2 }' )" authorized_keys > /dev/null || { | ||||
|   grep -F "$( echo $key | awk '{ print $2 }' )" authorized_keys || { | ||||
|     echo "$key" >> authorized_keys | ||||
|     chmod 600 authorized_keys | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| # this can be injected through the guest_api | ||||
| detee_ssh_key() { | ||||
|   local key='' | ||||
|   mkdir -p /mnt/root/.ssh | ||||
|   [[ -f "$SSH_KEY_FILE" ]] && while read -r key; do | ||||
|     grep -F "$( echo $key | awk '{ print $2 }' )" authorized_keys || { | ||||
|       echo "$key" >> authorized_keys | ||||
|     } | ||||
|   done < "$SSH_KEY_FILE" | ||||
|   chmod 600 authorized_keys | ||||
| } | ||||
| 
 | ||||
| mount_root() { | ||||
|   cryptsetup open -d $ROOT_KEYFILE /dev/vda1 root | ||||
|   mount /dev/mapper/root /mnt | ||||
| } | ||||
| @ -11,13 +11,9 @@ if [ -z $1 ]; then | ||||
| fi | ||||
| 
 | ||||
| server="$1" | ||||
| scp_server="$1" | ||||
| 
 | ||||
| echo $1 | grep -F ':' > /dev/null && scp_server="[${1}]" | ||||
| 
 | ||||
| if ! [ -z $2 ]; then | ||||
|   server="${2}@${server}" | ||||
|   echo $1 | grep -F ':' > /dev/null && scp_server="${2}@${scp_server}" | ||||
| fi | ||||
| 
 | ||||
| if [ -z $3 ]; then | ||||
| @ -28,24 +24,20 @@ else | ||||
|   scp="scp -P $3" | ||||
| fi | ||||
| 
 | ||||
| echo | ||||
| echo Starting installation... | ||||
| echo | ||||
| 
 | ||||
| set -e  | ||||
| 
 | ||||
| # TODO: test if this works | ||||
| mkdir -p tmp build | ||||
| my_location="$(pwd)" | ||||
| cd ../dtrfs_api && cargo build --release || { | ||||
|   echo Could not build dtrfs_api | ||||
|   exit 1 | ||||
| } | ||||
| cd "$my_location" | ||||
| cp ../dtrfs_api/target/release/dtrfs_api ./build/ | ||||
| tar cf tmp/dtrfs.tar *.sh build/dtrfs_api arch_guest_mods | ||||
| mkdir -p tmp | ||||
| tar cf tmp/dtrfs.tar *.sh | ||||
| $ssh $server rm -rf ${dir} | ||||
| $ssh $server mkdir -p ${dir} | ||||
| $scp tmp/dtrfs.tar ${scp_server}:${dir} | ||||
| $scp tmp/dtrfs.tar ${server}:${dir} | ||||
| $ssh $server tar -xf ${dir}/dtrfs.tar -C ${dir} | ||||
| $ssh $server GRAB_LOCAL_MODS=YES ${dir}/create.sh | ||||
| $ssh $server ${dir}/create.sh | ||||
| archive=$($ssh $server cat ${dir}/build/.archive_name) | ||||
| $scp ${scp_server}:${dir}/build/${archive} tmp/ | ||||
| $scp ${server}:${dir}/build/${archive} tmp/ | ||||
| echo | ||||
| echo initrd downloaded to: $(pwd)/tmp/${archive} | ||||
| @ -1,76 +0,0 @@ | ||||
| aesni_intel | ||||
| asn1_encoder | ||||
| async_tx | ||||
| async_xor | ||||
| atkbd | ||||
| cbc | ||||
| cdrom | ||||
| crc16 | ||||
| crc32c_generic | ||||
| crc32c_intel | ||||
| crc32_pclmul | ||||
| crct10dif_pclmul | ||||
| cryptd | ||||
| crypto_simd | ||||
| dm_bufio | ||||
| dm_crypt | ||||
| dm-integrity | ||||
| dm_integrity | ||||
| dm_mod | ||||
| efi_secret | ||||
| encrypted_keys | ||||
| ext4 | ||||
| failover | ||||
| gf128mul | ||||
| ghash_clmulni_intel | ||||
| i2c_i801 | ||||
| i2c_mux | ||||
| i2c_smbus | ||||
| i8042 | ||||
| intel_agp | ||||
| intel_gtt | ||||
| intel_pmc_bxt | ||||
| intel_rapl_common | ||||
| intel_rapl_msr | ||||
| ip_tables | ||||
| iTCO_vendor_support | ||||
| iTCO_wdt | ||||
| jbd2 | ||||
| libps2 | ||||
| loop | ||||
| lpc_ich | ||||
| mac_hid | ||||
| mbcache | ||||
| mousedev | ||||
| net_failover | ||||
| nfnetlink | ||||
| parport | ||||
| parport_pc | ||||
| pcspkr | ||||
| polyval_clmulni | ||||
| polyval_generic | ||||
| ppdev | ||||
| psmouse | ||||
| qemu_fw_cfg | ||||
| serio | ||||
| serio_raw | ||||
| sev-guest | ||||
| sev_guest | ||||
| sha1_ssse3 | ||||
| sha256 | ||||
| sha256_ssse3 | ||||
| sha512_ssse3 | ||||
| sr_mod | ||||
| tee | ||||
| trusted | ||||
| tsm | ||||
| virtio_blk | ||||
| virtio_net | ||||
| vivaldi_fmap | ||||
| vmw_vmci | ||||
| vmw_vsock_virtio_transport_common | ||||
| vmw_vsock_vmci_transport | ||||
| vsock | ||||
| vsock_loopback | ||||
| xor | ||||
| x_tables | ||||
| @ -1,15 +0,0 @@ | ||||
| #!/bin/bash | ||||
| script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) | ||||
| 
 | ||||
| # root of the initrd, that will be used to create the cpio archive | ||||
| [[ -n "$ROOT" ]] || | ||||
|   export ROOT="${script_dir}/build/initrd_root" | ||||
| # you need busybox to be able to instsall | ||||
| [[ -n "$BUSYBOX_PATH" ]] || | ||||
|   export BUSYBOX_PATH="/usr/lib/initcpio/busybox" | ||||
| # choose a kernel if you don't want to use the active one | ||||
| [[ -n "$KERNEL" ]] || | ||||
|   export KERNEL="$(uname -r)" | ||||
| # this will allow you to grab modules from the machine where the installer is running | ||||
| [[ -n "$GRAB_LOCAL_MODS" ]] || | ||||
|   export GRAB_LOCAL_MODS="no" | ||||
| @ -1,27 +0,0 @@ | ||||
| #!/bin/bash | ||||
| source /usr/lib/dtrfs/init_functions.sh | ||||
| 
 | ||||
| install_url="/tmp/detee_install_url" | ||||
| install_sha="/tmp/detee_install_sha" | ||||
| root_keyfile="/tmp/detee_root_keyfile" | ||||
| ssh_key_file="/tmp/detee_ssh_key" | ||||
| snp_key_file="/tmp/detee_luks_hotkey" | ||||
| 
 | ||||
| create_mounts | ||||
| load_modules | ||||
| setup_network | ||||
| 
 | ||||
| # load this module again cause it fails the first time | ||||
| modprobe sev_guest | ||||
| 
 | ||||
| create_certs | ||||
| dtrfs_api | ||||
| 
 | ||||
| github_ssh_key | ||||
| cp /etc/resolv.conf /mnt/etc/resolv.conf | ||||
| 
 | ||||
| # copy kernel modules in case the user deleted the old modules | ||||
| mkdir -p /mnt/lib/modules/ | ||||
| cp -rn /lib/modules/* /mnt/lib/modules/ | ||||
| 
 | ||||
| exec switch_root /mnt /sbin/init "$@" | ||||
| @ -1,60 +0,0 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| # This script is called by dtrfs_api to install an OS. | ||||
| 
 | ||||
| [[ -z "$INSTALL_URL" ]] && { | ||||
|   echo "Did not find INSTALL_URL env variable". | ||||
|   exit 1 | ||||
| } | ||||
| 
 | ||||
| [[ -z "$INSTALL_URL" ]] && { | ||||
|   echo "Did not find INSTALL_SHA env variable". | ||||
|   exit 2 | ||||
| } | ||||
| 
 | ||||
| [[ -f "$ROOT_KEYFILE" ]] || { | ||||
|   echo "Did not find keyfile at the following location: $ROOT_KEYFILE" | ||||
|   exit 3 | ||||
| } | ||||
| 
 | ||||
| # mount root if it exists | ||||
| blkid | grep vda1 | grep LUKS && { | ||||
|   echo "/dev/vda1 already has a LUKS partition" | ||||
|   exit 4 | ||||
| } | ||||
| 
 | ||||
| echo === Creating partition /dev/vda1 | ||||
| ( | ||||
|   echo n | ||||
|   echo p | ||||
|   echo  | ||||
|   echo  | ||||
|   echo  | ||||
|   echo w | ||||
| ) | fdisk /dev/vda | ||||
| echo "=== Formatting /dev/vda1 using cryptsetup luksFormat and opening as root" | ||||
| cryptsetup luksFormat --batch-mode -d $ROOT_KEYFILE /dev/vda1 || exit 5 | ||||
| [[ -f "$SNP_KEY_FILE" ]] && { | ||||
|   echo "Adding LUKS slot via SNP KDF key found at $SNP_KEY_FILE" | ||||
|   cryptsetup luksAddKey \ | ||||
|     --key-file $ROOT_KEYFILE \ | ||||
|     --new-keyfile $SNP_KEY_FILE /dev/vda1 | ||||
| } | ||||
| cryptsetup open -d $ROOT_KEYFILE /dev/vda1 root || exit 6 | ||||
| echo "=== Formatting /dev/mapper/root as ext4 and mounting at /mnt" | ||||
| mkfs.ext4 /dev/mapper/root || exit 7 | ||||
| mount /dev/mapper/root /mnt || exit 8 | ||||
| echo "=== Downloading OS template from $INSTALL_URL and verifying hash" | ||||
| wget -O /mnt/template.fsa "$INSTALL_URL" || { | ||||
|   echo "Failed to download $INSTALL_URL" | ||||
|   exit 9 | ||||
| } | ||||
| sha256sum /mnt/template.fsa | grep "${INSTALL_SHA}" || exit 1 | ||||
| echo "=== Installing OS template" | ||||
| fsarchiver restdir /mnt/template.fsa / | ||||
| rm /mnt/template.fsa | ||||
| # TODO: decide for UX if maybe we should allow user to inject fstab | ||||
| echo "" > /mnt/etc/fstab | ||||
| hostname=$(cat /proc/cmdline | grep -oE 'detee_name=[0-9a-z\_\.\-]+' | cut -d '=' -f2) | ||||
| echo "=== Setting up guest hostname as $hostname" | ||||
| [[ -n "$hostname" ]] && echo $hostname > /mnt/etc/hostname | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user