Compare commits

...

25 Commits

Author SHA1 Message Date
d88c175bd9
Switch license to https://unlicense.org/ 2025-08-30 12:13:49 +03:00
5f5c9ead51
add xt_REDIRECT kernel module 2025-04-16 18:45:56 +03:00
3d90a7b39b
allow injection of hostname via API 2025-03-21 23:05:23 +02:00
de94e74ab6
adding modules required for kubernetes 2025-03-12 05:34:46 +02:00
b49fa1bdc5
add support for wireguard 2025-03-09 03:17:00 +02:00
d3e13819a0
add more kernel modules, allowing docker publish 2025-02-28 03:47:01 +02:00
09765b7526
add more kernel modules required for docker stuff 2025-02-21 20:20:03 +02:00
b1fd8c355e
run ssh-keygen from dtrfs (instead of template) 2025-01-28 23:49:17 +02:00
60f1c8c2e2
changed admin encoding type to bs58 2025-01-23 06:06:46 +02:00
b028a2e947
create server key on install and allow download 2025-01-12 01:51:02 +02:00
7864c53236
allow multiple nics to be configured via cmdline 2024-12-14 16:41:47 +02:00
f9781c659b
new guest_api working 2024-11-28 21:35:56 +02:00
e212e9a99c
added guest_api and rewrote it based on new spec 2024-11-24 03:39:33 +02:00
0a2b4ce89d
move *.sh to the scripts folder 2024-11-22 16:00:01 +02:00
1be7fc516a
allow github usernames that contain uppercase 2024-11-15 22:51:35 +02:00
8f854993d6
improved handling of modules and cleaned a bit 2024-11-13 04:15:36 +02:00
68e25068b5
allow dtrfs to grab local mods from OS 2024-11-12 20:26:11 +02:00
ced0d86c4c
adding support for ipv6 to the install script 2024-11-12 18:34:32 +02:00
fa1591aad8
download compiled guest_api instead of building 2024-11-12 17:11:07 +02:00
8c8a60e821
allow hot key update even if install_url exists 2024-11-12 00:44:42 +02:00
39c2bdb9d8
added hot key decryption using SNP KDF 2024-11-10 17:05:21 +02:00
11a5b122c8
modprobe after you wait a bit 2024-11-10 04:19:47 +02:00
b426f1ed51
added support to inject ssh key via guest_api 2024-11-10 01:15:40 +02:00
7e3d33093a
encryption and decryption works 2024-11-09 22:41:45 +02:00
f86c6fb9fa
add readme on how to create OS template 2024-11-09 21:01:30 +02:00
21 changed files with 3540 additions and 163 deletions

3
.gitignore vendored

@ -1,3 +1,6 @@
# SPDX-License-Identifier: Unlicense
dtrfs.tar dtrfs.tar
build build
tmp tmp
target

24
LICENSE Normal file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>

41
README.md Normal file

@ -0,0 +1,41 @@
<!--
SPDX-License-Identifier: Unlicense
-->
## 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
- 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
- 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
- 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)"

2564
dtrfs_api/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

21
dtrfs_api/Cargo.toml Normal file

@ -0,0 +1,21 @@
# SPDX-License-Identifier: Unlicense
[package]
name = "dtrfs_api"
version = "0.1.0"
edition = "2021"
[dependencies]
bs58 = "0.5.1"
anyhow = "1.0.93"
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" }
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"] }
base64 = "0.22.1"

5
dtrfs_api/rustfmt.toml Normal file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Unlicense
reorder_impl_items = true
use_small_heuristics = "Max"
merge_imports = true

220
dtrfs_api/src/main.rs Normal file

@ -0,0 +1,220 @@
// SPDX-License-Identifier: Unlicense
mod os;
mod snp;
use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer};
use ed25519_dalek::{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_str =
key_str.strip_prefix("detee_admin=").ok_or("Could not get admin key from cmdline")?;
Ok(VerifyingKey::from_bytes(
&bs58::decode(key_str)
.into_vec()?
.try_into()
.map_err(|_| bs58::decode::Error::BufferTooSmall)?,
)?)
}
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 = bs58::decode(signature).into_vec()?;
let signature = Signature::from_bytes(signature.as_slice().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 {
hostname: String,
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, &form.hostname) {
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("/server_ssh_pubkeys")]
async fn get_server_pubkeys() -> HttpResponse {
match os::get_server_ssh_pubkeys() {
Ok(keys) => HttpResponse::Ok().body(keys),
Err(e) => HttpResponse::InternalServerError()
.body(format!("Could not get pubkeys due to error: {e:?}\nDid you install the OS?")),
}
}
#[get("/authorized_keys")]
async fn get_authorized_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("/authorized_keys")]
async fn post_authorized_keys(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_authorized_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(get_server_pubkeys)
.service(post_authorized_keys)
.service(get_authorized_keys)
.service(get_report)
.service(homepage)
})
.bind_rustls_0_23("[::]:22", config)?
.run()
.await
}

164
dtrfs_api/src/os.rs Normal file

@ -0,0 +1,164 @@
// SPDX-License-Identifier: Unlicense
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,
vm_hostname: &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)
.env("VM_HOSTNAME", vm_hostname)
.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_authorized_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 get_server_ssh_pubkeys() -> Result<String> {
let files = vec![
"/mnt/etc/ssh/ssh_host_rsa_key.pub",
"/mnt/etc/ssh/ssh_host_ecdsa_key.pub",
"/mnt/etc/ssh/ssh_host_ed25519_key.pub",
];
Ok(files
.iter()
.map(|f| std::fs::read_to_string(f))
.collect::<Result<String, std::io::Error>>()?)
}
pub fn list_ssh_keys() -> Result<String> {
Ok(std::fs::read_to_string("/mnt/root/.ssh/authorized_keys")?)
}

23
dtrfs_api/src/snp.rs Normal file

@ -0,0 +1,23 @@
// SPDX-License-Identifier: Unlicense
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))
}

18
init.sh

@ -1,18 +0,0 @@
#!/bin/bash
source /init_functions.sh
create_mounts
load_modules
create_certs
setup_network
# TODO: replace hardcoded URL with guest_api
echo "http://192.168.122.226/arch_base_dir.fsa" > /tmp/install_url
if [[ -f "/tmp/install_url" ]]; then
install_os
else
mount_root
fi
github_ssh_key
exec switch_root /mnt /sbin/init "$@"

@ -1,111 +0,0 @@
#!/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
}
create_mounts() {
mount -t proc proc /proc -o nosuid,noexec,nodev
mount -t sysfs sys /sys -o nosuid,noexec,nodev
mount -t devtmpfs dev /dev -o mode=0755,nosuid
mount -t tmpfs run /run -o nosuid,nodev,mode=0755
mkdir -m755 /run/initramfs
if [ -e /sys/firmware/efi ]; then
mount -t efivarfs efivarfs /sys/firmware/efi/efivars -o nosuid,nodev,noexec
fi
# Setup /dev symlinks
if [ -e /proc/kcore ]; then
ln -sfT /proc/kcore /dev/core
fi
ln -sfT /proc/self/fd /dev/fd
ln -sfT /proc/self/fd/0 /dev/stdin
ln -sfT /proc/self/fd/1 /dev/stdout
ln -sfT /proc/self/fd/2 /dev/stderr
}
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"
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" \
-days 365 -subj "$subject" 2>/dev/null
}
# expects kernel param in this format: detee_net=192.168.122.140_24_192.168.122.1_1.1.1.1
setup_network() {
local settings='' ip_addr='' mask='' cidr='' gateway='' nameserver=''
settings=$(cat /proc/cmdline | grep -oE 'detee_net=[0-9a-z\_\:\.]+' | cut -d '=' -f2)
# TODO: replace with exit 0 when you are ready to force a kernel panic
[[ -z "$settings" ]] && return 0
settings="${settings#detee_net=}"
ip_addr="$( echo ${settings} | cut -d'_' -f1 )"
mask="$( echo ${settings} | cut -d'_' -f2 )"
cidr="${ip_addr}/${mask}"
gateway="$( echo ${settings} | cut -d'_' -f3 )"
nameserver="$( echo ${settings} | cut -d'_' -f4 )"
ip addr add $cidr dev eth0
ip link set eth0 up
ip route add default via $gateway
echo nameserver $nameserver > /etc/resolv.conf
}
install_os() {
local url="$(cat /tmp/install_url)" hostname=''
blkid | grep vda1 | grep ext4 && {
mount_root
return 0
}
(
echo n
echo p
echo
echo
echo
echo w
) | fdisk /dev/vda
mkfs.ext4 /dev/vda1
mount_root
wget -O /mnt/template.fsa "$url"
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)
[[ -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 || {
echo "$key" >> authorized_keys
chmod 600 authorized_keys
}
}
mount_root() {
mkdir /mnt
mount /dev/vda1 /mnt
}

131
scripts/arch_guest_mods Normal file

@ -0,0 +1,131 @@
aesni_intel
asn1_encoder
async_tx
async_xor
atkbd
bridge
br_netfilter
cbc
cdrom
crc16
crc32c_generic
crc32c_intel
crc32_pclmul
crct10dif_pclmul
cryptd
crypto_simd
curve25519_x86_64
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
inet_diag
intel_agp
intel_gtt
intel_pmc_bxt
intel_rapl_common
intel_rapl_msr
ip6table_filter
ip6table_mangle
ip6table_nat
ip6_tables
ip6_udp_tunnel
ip_set
ip_set_hash_net
iptable_filter
iptable_mangle
iptable_nat
iptable_raw
ip_tables
ipt_REJECT
iTCO_vendor_support
iTCO_wdt
jbd2
libaesgcm
libchacha20poly1305
libcrc32c
libcurve25519_generic
libps2
llc
loop
lpc_ich
mac_hid
mbcache
mousedev
net_failover
nf_conntrack
nf_conntrack_netlink
nf_defrag_ipv4
nf_defrag_ipv6
nf_nat
nfnetlink
nfnetlink_acct
nfnetlink_log
nf_reject_ipv4
nf_tables
nft_chain_nat
nft_compat
overlay
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
stp
tcp_diag
tee
trusted
tsm
udp_tunnel
veth
virtio_blk
virtio_net
vivaldi_fmap
vmw_vmci
vmw_vsock_virtio_transport_common
vmw_vsock_vmci_transport
vsock
vsock_loopback
vxlan
wireguard
xfrm_algo
xfrm_user
xor
x_tables
xt_addrtype
xt_comment
xt_conntrack
xt_limit
xt_mark
xt_MASQUERADE
xt_multiport
xt_nat
xt_nfacct
xt_NFLOG
xt_physdev
xt_REDIRECT
xt_set
xt_tcpudp

@ -1,10 +1,16 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Unlicense
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
source creator_exports.sh source creator_exports.sh
source creator_functions.sh source creator_functions.sh
mkdir -p build mkdir -p build
cd build cd build
echo_cyan "Installing build dependencies..."
install_build_deps
echo_cyan "Starting installation at $ROOT." echo_cyan "Starting installation at $ROOT."
create_dirs create_dirs
@ -16,30 +22,31 @@ install_binary $(which openssl) && cp -r /etc/ssl "${ROOT}/etc/"
install_binary $(which cryptsetup) install_binary $(which cryptsetup)
install_binary $(which blkid) install_binary $(which blkid)
install_binary $(which fdisk) install_binary $(which fdisk)
install_binary $(which sysctl)
install_binary $(which mkfs.ext4) install_binary $(which mkfs.ext4)
install_binary $(which ssh-keygen)
install_binary $(which fsarchiver) install_binary $(which fsarchiver)
install_kmod install_kmod
install_busybox install_busybox
install_dtrfs_api
echo_cyan "Installing scripts..." echo_cyan "Installing scripts..."
install_init_script install_init_script
echo_cyan "Installing kernel modules..." echo_cyan "Installing base modules required to boot"
# # Uncomment this section if you want to grab modules from the guest OS
# scan_modules
install_module virtio_net install_module virtio_net
install_module ext4 install_module ext4
install_module virtio_blk install_module virtio_blk
install_module msr
install_module sev-guest install_module sev-guest
install_module dm_crypt install_module dm_crypt
install_module hid-generic
install_module dm-integrity install_module dm-integrity
install_module cbc
install_module hmac install_guest_mods
install_module sha256
install_module rng [[ "$GRAB_LOCAL_MODS" == "YES" ]] && {
install_module aes scan_modules
backup_active_modules
}
echo_cyan "Building module dependency tree..." echo_cyan "Building module dependency tree..."
cp /lib/modules/${KERNEL}/modules.{order,builtin,builtin.modinfo} "${ROOT}/lib/modules/${KERNEL}/" cp /lib/modules/${KERNEL}/modules.{order,builtin,builtin.modinfo} "${ROOT}/lib/modules/${KERNEL}/"

18
scripts/creator_exports.sh Executable file

@ -0,0 +1,18 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
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,5 +1,7 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Unlicense
echo_cyan() { echo_cyan() {
echo -e "\033[0;36m$1\033[0m" echo -e "\033[0;36m$1\033[0m"
} }
@ -16,13 +18,27 @@ echo_red() {
echo -e "\033[0;31m$1\033[0m" 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() { create_dirs() {
rm -rf "$ROOT" 2>/dev/null rm -rf "$ROOT" 2>/dev/null
mkdir -p "${ROOT}/usr/bin/"
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}/dev"
mkdir -p "${ROOT}/etc" mkdir -p "${ROOT}/etc"
mkdir -p "${ROOT}/mnt"
mkdir -p "${ROOT}/proc" mkdir -p "${ROOT}/proc"
mkdir -p "${ROOT}/run" mkdir -p "${ROOT}/run"
mkdir -p "${ROOT}/sys" mkdir -p "${ROOT}/sys"
@ -101,7 +117,8 @@ install_busybox() {
install_init_script() { install_init_script() {
cp ../init.sh "${ROOT}/init" 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() { install_module() {
@ -134,8 +151,28 @@ _install_module() {
done <<< "$( echo "$depends" )" 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() { scan_modules() {
local drivers='' mod='' local drivers='' mod=''
echo_yellow "Installing kernel modules based on current hardware..."
install_module "$(df -T / | awk '{ print $2 }' | tail -1)" install_module "$(df -T / | awk '{ print $2 }' | tail -1)"
drivers=$(lshw -c disk 2>/dev/null | grep -oE 'driver=[a-z\_\-]+' | cut -d '=' -f2; drivers=$(lshw -c disk 2>/dev/null | grep -oE 'driver=[a-z\_\-]+' | cut -d '=' -f2;
@ -147,14 +184,25 @@ scan_modules() {
done <<< "$( echo "$drivers" )" done <<< "$( echo "$drivers" )"
} }
install_dtrfs_api() {
local my_location="$(pwd)"
echo_cyan "Building dtrfs_api..."
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() { create_archive() {
local archive="detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz" local archive="detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz"
echo_cyan "Creating archive $archive" echo_cyan "Creating archive $(pwd)/$archive"
echo $archive > .archive_name echo $archive > .archive_name
my_location="$(pwd)" my_location="$(pwd)"
cd ${ROOT} cd ${ROOT}
find . | cpio -o -H newc | gzip \ find . | cpio -o -H newc | gzip \
> "${my_location}/detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz" > "${my_location}/detee-$(hostnamectl hostname)-${KERNEL}.cpio.gz"
cd $my_location cd "$my_location"
} }

30
scripts/init.sh Executable file

@ -0,0 +1,30 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
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 "$@"

100
scripts/init_functions.sh Normal file

@ -0,0 +1,100 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
load_modules() {
cat /load_modules.sh | bash
}
create_mounts() {
mount -t proc proc /proc -o nosuid,noexec,nodev
mount -t sysfs sys /sys -o nosuid,noexec,nodev
mount -t devtmpfs dev /dev -o mode=0755,nosuid
mount -t tmpfs run /run -o nosuid,nodev,mode=0755
mkdir -m755 /run/initramfs
if [ -e /sys/firmware/efi ]; then
mount -t efivarfs efivarfs /sys/firmware/efi/efivars -o nosuid,nodev,noexec
fi
# Setup /dev symlinks
if [ -e /proc/kcore ]; then
ln -sfT /proc/kcore /dev/core
fi
ln -sfT /proc/self/fd /dev/fd
ln -sfT /proc/self/fd/0 /dev/stdin
ln -sfT /proc/self/fd/1 /dev/stdout
ln -sfT /proc/self/fd/2 /dev/stderr
}
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"
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
}
setup_network_device() {
local device="$1" settings="$2" ip_addr='' mask='' cidr='' gateway=''
[[ -z "$settings" ]] && return 0
ip_addr="$( echo ${settings} | cut -d '_' -f1 )"
mask="$( echo ${settings} | cut -d '_' -f2 )"
cidr="${ip_addr}/${mask}"
gateway="$( echo ${settings} | cut -d '_' -f3 )"
ip addr add $cidr dev $device
ip link set $device up
sysctl -w net.ipv6.conf.$device.accept_ra=0
ip route add default via $gateway
sleep 2
ping -c 2 $gateway
}
# Expects kernel param in this format: detee_net=192.168.122.140_24_192.168.122.1_1.1.1.1
# In case the interface name is not specified, it defaults to eth0
# Supports manual device, for example: detee_net_eth1
# TODO: test if it is required to specify mac instead of device name
setup_network() {
local device_eth0_cfg='' device_cfg='' device_configs=''
sysctl -w net.ipv6.conf.all.accept_ra=0
sysctl -w net.ipv6.conf.default.accept_ra=0
# handle the default: detee_net=...
device_eth0_cfg=$(cat /proc/cmdline | grep -oE "detee_net=[0-9a-f\_\:\.]+" | cut -d '=' -f2)
[[ -z "$device_eth0_cfg" ]] || setup_network_device eth0 $device_eth0_cfg
# handle extra devices: detee_net_eth1=...
device_configs=$(cat /proc/cmdline| grep -oE "detee_net_[a-z0-9]*=[0-9a-f\_\:\.]+")
while read -r device_cfg; do
setup_network_device \
$(echo $device_cfg | cut -d '=' -f1 | cut -d '_' -f3) \
$(echo $device_cfg | cut -d '=' -f2)
done <<< "$( echo "$device_configs" )"
echo nameserver 1.1.1.1 >> /etc/resolv.conf
echo nameserver 1.0.0.1 >> /etc/resolv.conf
echo nameserver 2606:4700:4700::1111 >> /etc/resolv.conf
echo nameserver 2606:4700:4700::1001 >> /etc/resolv.conf
}
# 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)
[[ -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 || {
echo "$key" >> authorized_keys
chmod 600 authorized_keys
}
}

76
scripts/install_os.sh Executable file

@ -0,0 +1,76 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
# 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_SHA" ]] && {
echo "Did not find INSTALL_SHA env variable".
exit 2
}
[[ -z "$VM_HOSTNAME" ]] && {
echo "Did not find VM_HOSTNAME 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
echo "=== Setting up guest hostname as $VM_HOSTNAME"
echo $VM_HOSTNAME > /mnt/etc/hostname
echo "=== Generating SSH public keys"
echo "root:x:0:0:root:/root:/bin/sh" > /etc/passwd
[[ -f "/mnt/etc/ssh/ssh_host_rsa_key" ]] ||
ssh-keygen -t rsa -f /mnt/etc/ssh/ssh_host_rsa_key -N '' > /dev/null
[[ -f "/mnt/etc/ssh/ssh_host_ecdsa_key" ]] ||
ssh-keygen -t ecdsa -f /mnt/etc/ssh/ssh_host_ecdsa_key -N '' > /dev/null
[[ -f "/mnt/etc/ssh/ssh_host_ed25519_key" ]] ||
ssh-keygen -t ed25519 -f /mnt/etc/ssh/ssh_host_ed25519_key -N '' > /dev/null
echo "=== Done! Download keys from /server_pubkeys"

@ -1,4 +1,7 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Unlicense
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
dir="/tmp/dtrfs" dir="/tmp/dtrfs"
@ -11,9 +14,13 @@ if [ -z $1 ]; then
fi fi
server="$1" server="$1"
scp_server="$1"
echo $1 | grep -F ':' > /dev/null && scp_server="[${1}]"
if ! [ -z $2 ]; then if ! [ -z $2 ]; then
server="${2}@${server}" server="${2}@${server}"
echo $1 | grep -F ':' > /dev/null && scp_server="${2}@${scp_server}"
fi fi
if [ -z $3 ]; then if [ -z $3 ]; then
@ -24,20 +31,24 @@ else
scp="scp -P $3" scp="scp -P $3"
fi fi
echo
echo Starting installation...
echo
set -e set -e
mkdir -p tmp # TODO: test if this works
tar cf tmp/dtrfs.tar *.sh 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 rm -rf ${dir}
$ssh $server mkdir -p ${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 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) archive=$($ssh $server cat ${dir}/build/.archive_name)
$scp ${server}:${dir}/build/${archive} tmp/ $scp ${scp_server}:${dir}/build/${archive} tmp/
echo echo
echo initrd downloaded to: $(pwd)/tmp/${archive} echo initrd downloaded to: $(pwd)/tmp/${archive}

27
scripts/upload_to_registry.sh Executable file

@ -0,0 +1,27 @@
#!/bin/bash
# SPDX-License-Identifier: Unlicense
kernel_path="/boot/vmlinuz-linux"
dtrfs_path="$1"
dtrfs_name=$(basename $dtrfs_path)
dtrfs_sha=$(sha256sum $dtrfs_path | awk '{ print $1 }')
kernel_name="vmlinuz-linux-$(uname -r)"
kernel_sha=$(sha256sum $kernel_path | awk '{ print $1 }')
scp $dtrfs_path registry.detee.ltd:/var/www/html/${dtrfs_name}
ssh registry.detee.ltd ln -s $dtrfs_name /var/www/html/${dtrfs_sha}
scp $kernel_path registry.detee.ltd:/var/www/html/${kernel_name}
ssh registry.detee.ltd ln -s $kernel_name /var/www/html/${kernel_sha}
echo "Also add this to detee-cli/src/snp/mod.rs"
echo "
name: \"dtrfs-$(uname -r)\".to_string(),
vendor: \"ghe0\".to_string(),
dtrfs_url: \"http://registry.detee.ltd/${dtrfs_name}\".to_string(),
dtrfs_sha: \"${dtrfs_sha}\".to_string(),
kernel_url: \"http://registry.detee.ltd/${kernel_name}\".to_string(),
kernel_sha: \"${kernel_sha}\".to_string()
"