[demo] Add runtime mount and boot occlum instance demo

This commit is contained in:
Zheng, Qi 2022-05-30 10:54:53 +08:00 committed by volcano
parent 2347951743
commit 926d80430b
9 changed files with 398 additions and 0 deletions

@ -0,0 +1,75 @@
# Runtime boot pre-generated UnionFS image
Generally, every Occlum instance has to pass the `Occlum build` process.
In some scenarios, mount and boot a pre-generated UnionFS image without `Occlum build` is a good feature. This demo introduces a way to runtime boot a BASH demo.
## Flow
### First, build a [`BASH`](../bash) Occlum instance
The later step will use the image content to generate UnionFS image.
### Build and start a [`gen_rootfs`](./gen_rootfs) Occlum instance
This `gen_rootfs` mounts a empty UnionFS, copy the BASH Occlum image content to the mount point, unmount the UnionFS. It generates an encrypted UnionFS image containing the BASH image content. The **key** used in this demo is `"c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61"`.
### Build customized [`init`](./init)
Occlum [`default init`](../../tools/init) calls syscall (363) `MountRootFS` to mount and boot Occlum instance generated by normal `occlum build`.
```
(MountRootFS = 363) => do_mount_rootfs(key_ptr: *const sgx_key_128bit_t, rootfs_config: *const user_rootfs_config)
```
The first parameter `key_ptr` is optional.
The second parameter `rootfs_config` needs to be set as NULL.
But for runtime booting pre-generated UnionFS image, The first parameter `key_ptr` is must to have, the second parameter `rootfs_config` needs have valid members.
```
struct user_rootfs_config {
// UnionFS type rootfs upper layer, read-write layer
upper_layer_path: *const i8,
// UnionFS type rootfs lower layer, read-only layer
lower_layer_path: *const i8,
entry_point: *const i8,
// HostFS source path
hostfs_source: *const i8,
// HostFS target path, default value is "/host"
hostfs_target: *const i8,
}
```
In this demo, parameters values are provided as below.
* **rootfs_key**
The key to encrypt/decrypt the rootfs, here it is `"c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61".
* **rootfs_upper_layer**
The upper layer path of the unionfs type rootfs. In this case, it is relative path `"../gen_rootfs_instance/mnt_unionfs/upper"`.
* **rootfs_lower_layer**
The lower layer path of the unionfs type rootfs. In this case, it is relative path `"../gen_rootfs_instance/mnt_unionfs/lower"`.
* **rootfs_entry**
The entry point of the rootfs. In his case, it is `"/bin"`.
* **hostfs_source**
It is set to be `/tmp` in this case.
In this example customized init, the above parameters are declared in the source [`main.rs`](./init/src/main.rs). In real case, they could be acquired by LA/RA or by modifying the PAL api `pal_run_init_process`.
### Build a boot template Occlum instance
This template uses the customized init. The RootFS image is not important, which will be replaced during boot.
All above steps could be done with one [`script`](./build_content.sh).
```
./build_content.sh
```
After running the script, runtime boot BASH could be done as below even if the default RootFS image has no BASH function.
```
# cd boot_instance
# occlum run /bin/occlum_bash_test.sh
```

@ -0,0 +1,7 @@
includes:
- base.yaml
targets:
- target: /bin
copy:
- files:
- /usr/bin/hostname

@ -0,0 +1,90 @@
#!/bin/bash
set -e
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
export BASH_DEMO_DIR="${script_dir}/../bash"
export INIT_DIR="${script_dir}/init"
UNIONFS_DIR="${script_dir}/gen_rootfs_instance/mnt_unionfs"
ENCRIP_KEY="c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61"
function build_bash_demo()
{
pushd ${BASH_DEMO_DIR}
rm -rf occlum_instance && occlum new occlum_instance
cd occlum_instance
rm -rf image
copy_bom -f ../bash.yaml --root image --include-dir /opt/occlum/etc/template
new_json="$(jq '.resource_limits.user_space_size = "600MB" |
.resource_limits.kernel_space_stack_size ="2MB" ' Occlum.json)" && \
echo "${new_json}" > Occlum.json
occlum build
popd
}
function build_init()
{
pushd ${INIT_DIR}
occlum-cargo clean
occlum-cargo build --release
popd
}
function build_and_gen_rootfs()
{
pushd gen_rootfs
cargo build
popd
# initialize occlum workspace
rm -rf gen_rootfs_instance && occlum new gen_rootfs_instance
pushd gen_rootfs_instance
new_json="$(jq '.resource_limits.user_space_size = "1000MB" |
.resource_limits.kernel_space_heap_size= "512MB" |
.resource_limits.kernel_space_stack_size= "16MB" ' Occlum.json)" && \
echo "${new_json}" > Occlum.json
rm -rf image
copy_bom -f ../gen_rootfs.yaml --root image --include-dir /opt/occlum/etc/template
occlum build
mkdir -p mnt_unionfs/lower
mkdir -p mnt_unionfs/upper
mkdir rootfs
cp -rf ${BASH_DEMO_DIR}/occlum_instance/image/* rootfs/
occlum run /bin/gen_rootfs ${ENCRIP_KEY}
popd
}
function build_boot_template()
{
rm -rf boot_instance && occlum new boot_instance
pushd boot_instance
new_json="$(jq '.resource_limits.user_space_size = "600MB" |
.resource_limits.kernel_space_stack_size ="2MB" ' Occlum.json)" && \
echo "${new_json}" > Occlum.json
rm -rf image
copy_bom -f ../boot_template.yaml --root image --include-dir /opt/occlum/etc/template
# Update init
rm -rf initfs
copy_bom -f ../init.yaml --root initfs --include-dir /opt/occlum/etc/template
occlum build
popd
}
build_bash_demo
build_and_gen_rootfs
build_init
build_boot_template

@ -0,0 +1,7 @@
includes:
- base.yaml
targets:
- target: /bin
copy:
- files:
- ../gen_rootfs/target/debug/gen_rootfs

@ -0,0 +1,11 @@
[package]
name = "gen_rootfs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2"
nix = "0.23.0"
fs_extra = "1.2"

@ -0,0 +1,74 @@
use std::env;
use std::fs;
use std::path::Path;
use nix::mount::{mount, umount, MsFlags};
use fs_extra::dir::{copy, CopyOptions};
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
fs::create_dir("/mount").unwrap();
let fs_type = "unionfs";
let mount_path = Path::new("/mount");
let source = Path::new("unionfs");
let flags = MsFlags::empty();
let key = &args[1];
let options = format!(
"lowerdir={},upperdir={},key={}",
"./mnt_unionfs/lower",
"./mnt_unionfs/upper",
key
);
println!("{:#?} {:#?} {:#?} {:#?} {:#?}", source, mount_path, fs_type, flags, options.as_str());
mount(
Some(source),
mount_path,
Some(fs_type),
flags,
Some(options.as_str()),
).unwrap();
println!("Copy rootfs content to /mount");
let copy_options = CopyOptions::new();
let paths = fs::read_dir("/host/rootfs").unwrap();
for entry in paths {
let path = entry.unwrap().path();
println!("Name: {}", path.display());
copy(path, "/mount", &copy_options).unwrap();
}
println!("List directories in {:#?}", mount_path);
let paths = fs::read_dir("/mount").unwrap();
for path in paths {
println!("Name: {}", path.unwrap().path().display())
}
println!("Unmount {:#?}", mount_path);
umount(mount_path).unwrap();
println!("Do mount again");
fs::create_dir("/mount2").unwrap();
let mount_path = Path::new("/mount2");
println!("{:#?} {:#?} {:#?} {:#?} {:#?}", source, mount_path, fs_type, flags, options.as_str());
mount(
Some(source),
mount_path,
Some(fs_type),
flags,
Some(options.as_str()),
).unwrap();
println!("List directories in {:#?}", mount_path);
let paths = fs::read_dir("/mount2").unwrap();
for path in paths {
println!("Name: {}", path.unwrap().path().display())
}
println!("Unmount {:#?}", mount_path);
umount(mount_path).unwrap();
}

@ -0,0 +1,7 @@
includes:
- base.yaml
targets:
- target: /bin/
copy:
- files:
- ${INIT_DIR}/target/x86_64-unknown-linux-musl/release/init

@ -0,0 +1,10 @@
[package]
name = "init"
version = "0.0.1"
authors = ["LI Qing geding.lq@antgroup.com"]
edition = "2018"
[dependencies]
libc = "0.2.84"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

@ -0,0 +1,117 @@
extern crate libc;
extern crate serde;
extern crate serde_json;
use libc::syscall;
use std::io::prelude::*;
use std::error::Error;
use std::fs::File;
use std::io::{ErrorKind, Read};
use std::ffi::CString;
use std::env;
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
// TODO: Get the rootfs key and other parameters through RA/LA or PAL
let rootfs_key = b"c7-32-b3-ed-44-df-ec-7b-25-2d-9a-32-38-8d-58-61";
let rootfs_upper_layer = "../gen_rootfs_instance/mnt_unionfs/upper";
let rootfs_lower_layer = "../gen_rootfs_instance/mnt_unionfs/lower";
let rootfs_entry = "/bin";
// Get the key of FS image if needed
let key = {
// TODO: Get the key through RA or LA
let mut file = File::create("/etc/image_key")?;
// Writes key.
file.write(rootfs_key)?;
const IMAGE_KEY_FILE: &str = "/etc/image_key";
let key_str = load_key(IMAGE_KEY_FILE)?;
let mut key: sgx_key_128bit_t = Default::default();
parse_str_to_bytes(&key_str, &mut key)?;
Some(key)
};
let key_ptr = key
.as_ref()
.map(|key| key as *const sgx_key_128bit_t)
.unwrap_or(std::ptr::null());
// Mount the image
const SYS_MOUNT_FS: i64 = 363;
// Set rootfs parameters
let upper_layer_path =
CString::new(rootfs_upper_layer).expect("CString::new failed");
let lower_layer_path =
CString::new(rootfs_lower_layer).expect("CString::new failed");
let entry_point = CString::new(rootfs_entry).expect("CString::new failed");
let hostfs_source = CString::new("/tmp").expect("CString::new failed");
let rootfs_config: user_rootfs_config = user_rootfs_config {
upper_layer_path: upper_layer_path.as_ptr(),
lower_layer_path: lower_layer_path.as_ptr(),
entry_point: entry_point.as_ptr(),
hostfs_source: hostfs_source.as_ptr(),
hostfs_target: std::ptr::null()
};
let ret = unsafe { syscall(
SYS_MOUNT_FS,
key_ptr,
&rootfs_config)
};
if ret < 0 {
return Err(Box::new(std::io::Error::last_os_error()));
}
Ok(())
}
#[allow(non_camel_case_types)]
type sgx_key_128bit_t = [u8; 16];
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[allow(non_camel_case_types)]
struct user_rootfs_config {
// UnionFS type rootfs upper layer, read-write layer
upper_layer_path: *const i8,
// UnionFS type rootfs lower layer, read-only layer
lower_layer_path: *const i8,
entry_point: *const i8,
// HostFS source path
hostfs_source: *const i8,
// HostFS target path, default value is "/host"
hostfs_target: *const i8,
}
fn load_key(key_path: &str) -> Result<String, Box<dyn Error>> {
let mut key_file = File::open(key_path)?;
let mut key = String::new();
key_file.read_to_string(&mut key)?;
Ok(key.trim_end_matches(|c| c == '\r' || c == '\n').to_string())
}
fn parse_str_to_bytes(arg_str: &str, bytes: &mut [u8]) -> Result<(), Box<dyn Error>> {
let bytes_str_vec = {
let bytes_str_vec: Vec<&str> = arg_str.split('-').collect();
if bytes_str_vec.len() != bytes.len() {
return Err(Box::new(std::io::Error::new(
ErrorKind::InvalidData,
"The length or format of Key/MAC string is invalid",
)));
}
bytes_str_vec
};
for (byte_i, byte_str) in bytes_str_vec.iter().enumerate() {
bytes[byte_i] = u8::from_str_radix(byte_str, 16)?;
}
Ok(())
}