commit ba5bfdd66e9d0d6dc91aa4d08b8366bc9e5a517f Author: ghe0 Date: Wed Dec 4 00:52:31 2024 +0200 first push diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dd88ee5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,82 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" + +[[package]] +name = "cidr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc95a0c21d5409adc146dbbb152b5c65aaea32bc2d2f57cf12f850bffdd7ab8" +dependencies = [ + "serde", +] + +[[package]] +name = "daemon" +version = "0.1.0" +dependencies = [ + "anyhow", + "cidr", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0ce230b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "daemon" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.94" +cidr = { version = "0.3.0", features = ["serde"] } +serde = { version = "1.0.215", features = ["derive"] } diff --git a/scripts/start_qemu_vm.sh b/scripts/start_qemu_vm.sh new file mode 100644 index 0000000..3716258 --- /dev/null +++ b/scripts/start_qemu_vm.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +[[ -z "$VM_UUID" ]] || { + echo "Environment variable VM_UUID is not set." + exit 1 +} +source "/etc/detee/daemon/vms/$VM_UUID" + +mandatory_vars=("IF_DEVICE" "IF_NAME" "IF_TYPE" "KERNEL" \ + "INITRD" "PARAMS" "CPU_TYPE" "VCPUS" "MEMORY" \ + "MAX_MEMORY" "DISK" "DISK_SIZE") +for var in "${mandatory_vars[@]}"; do + if [ -z "${!var}" ]; then + echo "Environment variable $var is not set." + exit 1 + fi +done + +if [[ "$IF_TYPE" == "macvtap" || "$IF_TYPE" == "ipvtap" ]]; then + ip link add link $IF_DEVICE name $IF_NAME type $IF_TYPE mode bridge + ip link set $IF_NAME up + ip link set $IF_NAME promisc on + vtap_index="$(cat /sys/class/net/${IF_NAME}/ifindex)" + vtap_addr="$(cat /sys/class/net/${IF_NAME}/address)" + qemu_device_params="-netdev tap,id=hostnet1,fd=3 3<>/dev/tap${macvtap_index}" + qemu_device_params+=" -device virtio-net-pci,netdev=hostnet1,mac=${macvtap_addr},romfile=" +fi + +if [[ "$IF_TYPE" == "NAT" ]]; then + ports="" + for port_pair in "$NAT_PORT_FW"; do + host_port="$( echo $port_pair | cut -d ':' -f1 )" + guest_port="$( echo $port_pair | cut -d ':' -f2 )" + ports+=",hostfwd=tcp::${host_port}-:${guest_port}" + done + qemu_device_params="-netdev user,id=vmnic${ports}" + qemu_device_params+=" -device virtio-net-pci,netdev=vmnic,romfile=" +fi + +# TODO: also handle bridge device (when IPs are public, but the host is the gateway) + +vm_disk="/root/dtrfs/arch-1-ghe0.qcow2" + +[[ -f $DISK ]] || qemu-img create -f qcow2 ${DISK} ${DISK_SIZE} + +qemu-system-x86_64 $qemu_device_params \ + -enable-kvm -cpu $CPU_TYPE -vga none \ + -machine q35,confidential-guest-support=sev0,memory-backend=ram1 \ + -smp $VCPUS,maxcpus=255 -m $MEMORY,slots=5,maxmem=$MAX_MEMORY \ + -no-reboot -bios /usr/share/edk2/ovmf/OVMF.amdsev.fd \ + -drive file=${DISK},if=none,id=disk0,format=qcow2 \ + -device virtio-blk-pci,drive=disk0 \ + -object memory-backend-memfd,id=ram1,size=$MEMORY,share=true,prealloc=false \ + -object sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on \ + -kernel $KERNEL -append "$PARAMS" -initrd $INITRD \ + -nographic -monitor pty -serial mon:stdio -monitor unix:monitor,server,nowait diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..6b994e0 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,36 @@ +#![allow(dead_code)] +use cidr::Ipv4Cidr; +use cidr::Ipv6Cidr; +use core::net::Ipv4Addr; +use core::net::Ipv6Addr; + +struct Volume { + path: String, + // maximum allowed storage in MB + max_storage: u64, +} + +struct Interface { + r#type: InterfaceType, + name: String, + ipv4_ranges: Vec, + reserved_v4_addrs: Vec, + ipv6_ranges: Vec, + reserved_v6_addrs: Vec, + // TODO: add bandwidth +} + +enum InterfaceType { + NAT, + MACVTAP, + IPVTAP, + Bridge, +} + +struct Config { + max_cores_per_vm: u64, + max_cpu_reservation: u64, + max_mem_reservation: u64, + network_interfaces: Vec, + volumes: Vec, +} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..ee60b2c --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,7 @@ +#![allow(dead_code)] + +pub(crate) const DEFAULT_OVMF: &str = "/usr/share/edk2/ovmf/OVMF.amdsev.fd"; +pub(crate) const BOOT_DIR: &str = "/var/lib/libvirt/detee/"; +pub(crate) const VM_CONFIG_DIR: &str = "/etc/detee/daemon/vms/"; +pub(crate) const CONFIG_PATH: &str = "/etc/detee/daemon/config.json"; +pub(crate) const START_VM_SH: &str = "/usr/local/bin/detee/start_qemu_vm.sh"; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..07488b2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,7 @@ +mod config; +mod state; +mod constants; + +fn main() { + println!("Hello, world!"); +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..4a649eb --- /dev/null +++ b/src/state.rs @@ -0,0 +1,131 @@ +#![allow(dead_code)] +use std::fs::remove_file; +use crate::constants::*; +use anyhow::Result; +use std::fs::File; +use std::io::Write; + +enum NIC { + NAT { device: String }, + // TODO: figure how to calculate IF_NAME based on index + MACVTAP { name: String, device: String }, + IPVTAP { name: String, device: String }, + Bridge { device: String }, +} + +impl NIC { + fn if_type(&self) -> String { + match self { + NIC::IPVTAP { .. } => format!("ipvtap"), + NIC::MACVTAP { .. } => format!("macvtap"), + NIC::NAT { .. } => format!("nat"), + NIC::Bridge { .. } => format!("bridge"), + } + } + + fn device_name(&self) -> String { + match self { + NIC::IPVTAP { device, .. } => device.clone(), + NIC::MACVTAP { device, .. } => device.clone(), + NIC::NAT { device, .. } => device.clone(), + NIC::Bridge { device, .. } => device.clone(), + } + } + + fn vtap_name(&self) -> Option { + match self { + NIC::IPVTAP { name, .. } => Some(name.clone()), + NIC::MACVTAP { name, .. } => Some(name.clone()), + NIC::NAT { .. } | NIC::Bridge { .. } => None, + } + } + + fn is_vtap(&self) -> bool { + match self { + NIC::IPVTAP { .. } | NIC::MACVTAP { .. } => true, + NIC::NAT { .. } | NIC::Bridge { .. } => false, + } + } +} + +struct VM { + uuid: String, + hostname: String, + ip: String, + // requires short format (example: 24) + subnet: String, + gateway: String, + nameserver: String, + admin_key: String, + // TODO: add support for multiple NICs + nic: NIC, + cpu_type: String, + vcpus: u32, + // memory in MB + memory: u32, + disk_absolute_path: String, + // disk size in GB + disk_size: u32, + kernel_path: String, + initrd_path: String, + ovmf_path: String, +} + +impl VM { + pub fn kernel_params(&self) -> String { + let ip_string = format!( + "detee_net={}_{}_{}_{}", + self.ip, self.subnet, self.gateway, self.nameserver + ); + let admin_key = format!("detee_admin={}", self.admin_key); + let hostname = format!("detee_name={}", self.hostname); + format!("{} {} {}", ip_string, admin_key, hostname) + } + + pub fn export_vm_env(&self) -> String { + let mut vars = String::new(); + + vars += &format!("IF_DEVICE={}\n", self.nic.device_name()); + if let Some(vtap_name) = self.nic.vtap_name() { + vars += &format!("IF_NAME={}\n", vtap_name); + } + vars += &format!("IF_TYPE={}\n", self.nic.if_type()); + vars += &format!("KERNEL={}\n", self.kernel_path); + vars += &format!("INITRD={}\n", self.initrd_path); + vars += &format!("PARAMS={}\n", self.kernel_params()); + vars += &format!("CPU_TYPE={}\n", self.cpu_type); + vars += &format!("VCPUS={}\n", self.vcpus); + vars += &format!("MEMORY={}MB\n", self.memory); + vars += &format!("MAX_MEMORY={}MB\n", self.memory + 256); + vars += &format!("DISK={}\n", self.disk_absolute_path); + vars += &format!("DISK_SIZE={}GB\n", self.disk_size); + + todo!(); + } + + pub fn write_systemd_unit_file(&self) -> Result<()> { + let mut contents = String::new(); + contents += &format!("[Unit]"); + contents += &format!("Description=DeTEE {}", self.uuid); + contents += &format!("After=network.target"); + contents += &format!(""); + contents += &format!("[Service]"); + contents += &format!("Type=simple"); + contents += &format!("Environment=VM_UUID={}", self.uuid); + contents += &format!("ExecStart={}", START_VM_SH); + contents += &format!("ExecStop=/bin/kill -s SIGINT $MAINPID"); + contents += &format!("Restart=always"); + contents += &format!(""); + contents += &format!("[Install]"); + contents += &format!("WantedBy=multi-user.target"); + + let mut file = File::create(VM_CONFIG_DIR.to_string() + "/" + &self.uuid)?; + file.write_all(contents.as_bytes())?; + Ok(()) + } + + pub fn delete_systemd_unit_file(&self) -> Result<()> { + remove_file(VM_CONFIG_DIR.to_string() + "/" + &self.uuid)?; + Ok(()) + } +}