Compare commits

...

10 Commits

18 changed files with 3221 additions and 143 deletions

1
.gitignore vendored

@ -1,3 +1,4 @@
dtrfs.tar
build
tmp
target

@ -1,17 +1,37 @@
## OS template
You will need a working OS template to work with this project.
Easy solution create an OS template:
- mount the archlinux installation .iso in a VM
- run `pacstrap /mnt base linux openssh`
- start any archlinux machine (the arch installer also works)
- install `arch-install-scripts`
- run `pacstrap /mnt base openssh` to install base packages to /mnt
- 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`
- download `/tmp/os_template.fsa`
- run `fsarchiver savedir /tmp/os_template.fsa /mnt` to save your OS template
- download `/tmp/os_template.fsa` to your machine
- upload the `os_template.fsa` anywhere so that it can be downloaded with wget
Some notes on the above:
- base and linux are the only packages to run a VM
- base is the only package required to run a dtrfs VM; the kernel is not needed cause we are using SNP
- 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.

@ -1,7 +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
export ROOT="${script_dir}/build/initrd_root"
export BUSYBOX_PATH="/usr/lib/initcpio/busybox"
export KERNEL="$(uname -r)"

2537
dtrfs_api/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
dtrfs_api/Cargo.toml Normal file

@ -0,0 +1,18 @@
[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"] }

3
dtrfs_api/rustfmt.toml Normal file

@ -0,0 +1,3 @@
reorder_impl_items = true
use_small_heuristics = "Max"
merge_imports = true

205
dtrfs_api/src/main.rs Normal file

@ -0,0 +1,205 @@
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
}

147
dtrfs_api/src/os.rs Normal file

@ -0,0 +1,147 @@
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")?)
}

21
dtrfs_api/src/snp.rs Normal file

@ -0,0 +1,21 @@
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

@ -1,30 +0,0 @@
#!/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 "$@"

76
scripts/arch_guest_mods Normal file

@ -0,0 +1,76 @@
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

@ -5,6 +5,9 @@ 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
@ -20,27 +23,25 @@ install_binary $(which mkfs.ext4)
install_binary $(which fsarchiver)
install_kmod
install_busybox
install_guest_api
install_dtrfs_api
echo_cyan "Installing scripts..."
install_init_script
echo_cyan "Installing kernel modules..."
# # Uncomment this section if you want to grab modules from the guest OS
# scan_modules
echo_cyan "Installing base modules required to boot"
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_module cbc
install_module hmac
install_module sha256
install_module rng
install_module aes
install_guest_mods
[[ "$GRAB_LOCAL_MODS" == "YES" ]] && {
scan_modules
backup_active_modules
}
echo_cyan "Building module dependency tree..."
cp /lib/modules/${KERNEL}/modules.{order,builtin,builtin.modinfo} "${ROOT}/lib/modules/${KERNEL}/"

15
scripts/creator_exports.sh Executable file

@ -0,0 +1,15 @@
#!/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"

@ -16,11 +16,24 @@ 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"
mkdir -p "${ROOT}/usr/lib/dtrfs"
mkdir -p "${ROOT}/dev"
mkdir -p "${ROOT}/etc"
mkdir -p "${ROOT}/mnt"
@ -33,9 +46,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.
@ -102,7 +115,8 @@ install_busybox() {
install_init_script() {
cp ../init.sh "${ROOT}/init"
cp ../init_functions.sh "${ROOT}/"
cp ../init_functions.sh "${ROOT}/usr/lib/dtrfs/"
cp ../install_os.sh "${ROOT}/usr/lib/dtrfs/"
}
install_module() {
@ -135,8 +149,28 @@ _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;
@ -148,29 +182,24 @@ scan_modules() {
done <<< "$( echo "$drivers" )"
}
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
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"
}
create_archive() {
local archive="detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz"
echo_cyan "Creating archive $archive"
echo_cyan "Creating archive build/$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"
}

27
scripts/init.sh Executable file

@ -0,0 +1,27 @@
#!/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,13 +1,5 @@
#!/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
}
@ -35,14 +27,15 @@ create_mounts() {
create_certs() {
cert_dir="/tmp/certs"
key="$cert_dir/guest_api.key"
cert="$cert_dir/guest_api.crt"
subject="/C=W3/O=DeTEE/OU=COCO/CN=guest-api"
key="$cert_dir/dtrfs_api.key"
cert="$cert_dir/dtrfs_api.crt"
subject="/C=W3/O=DeTEE/OU=COCO/CN=dtrfs-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
}
@ -63,68 +56,22 @@ setup_network() {
ip link set eth0 up
ip route add default via $gateway
echo nameserver $nameserver > /etc/resolv.conf
sleep 4
sleep 2
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-z\_\.\-]+' | cut -d '=' -f2)
github_user=$(cat /proc/cmdline | grep -oE 'detee_ghu=[0-9a-zA-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 || {
grep -F "$( echo $key | awk '{ print $2 }' )" authorized_keys > /dev/null || {
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
}

60
scripts/install_os.sh Executable file

@ -0,0 +1,60 @@
#!/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

@ -11,9 +11,13 @@ 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
@ -24,20 +28,24 @@ else
scp="scp -P $3"
fi
echo
echo Starting installation...
echo
set -e
mkdir -p tmp
tar cf tmp/dtrfs.tar *.sh
# 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
$ssh $server rm -rf ${dir}
$ssh $server mkdir -p ${dir}
$scp tmp/dtrfs.tar ${server}:${dir}
$scp tmp/dtrfs.tar ${scp_server}:${dir}
$ssh $server tar -xf ${dir}/dtrfs.tar -C ${dir}
$ssh $server ${dir}/create.sh
$ssh $server GRAB_LOCAL_MODS=YES ${dir}/create.sh
archive=$($ssh $server cat ${dir}/build/.archive_name)
$scp ${server}:${dir}/build/${archive} tmp/
$scp ${scp_server}:${dir}/build/${archive} tmp/
echo
echo initrd downloaded to: $(pwd)/tmp/${archive}