Skip to content

Commit

Permalink
Merge pull request #129 from ariel-miculas/lxc_integration
Browse files Browse the repository at this point in the history
Lxc integration
  • Loading branch information
ariel-miculas authored Sep 18, 2024
2 parents 9b32174 + 8726837 commit 4afc76a
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 70 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ $ tree /tmp/example-rootfs

Then run:
```
$ cargo run --release -- build /tmp/example-rootfs /tmp/puzzlefs-image puzzlefs_example
$ cargo run --release -- build /tmp/example-rootfs /tmp/puzzlefs-image:puzzlefs_example
puzzlefs image manifest digest: 9ac9abc098870c55cc61431dae8635806273d8f61274d34bec062560e79dc2f5
```
This builds a puzzlefs image with the above root filesystem in `/tmp/puzzlefs-image`, with the tag `puzzlefs_example`.
Expand All @@ -107,13 +107,13 @@ It also outputs the image's manifest digest, which is useful for verifying the i
For additional build options, run `puzzlefs build -h`.

### Mounting a puzzlefs image
To mount the above puzlefs image, first we need to create a mountpoint:
To mount the above puzzlefs image, first we need to create a mountpoint:
```
mkdir /tmp/mounted-image
```
Then run `puzzlefs mount` with the location of the puzzlefs image, the image tag and the mountpoint:
```
$ cargo run --release -- mount /tmp/puzzlefs-image puzzlefs_example /tmp/mounted-image
$ cargo run --release -- mount /tmp/puzzlefs-image:puzzlefs_example /tmp/mounted-image
```

If everything was successful, you will see a `fuse` entry in the output of `mount`:
Expand Down Expand Up @@ -145,7 +145,7 @@ For additional mount options, run `cargo run -- mount -h`.
### Mounting with fs-verity enabled
If you want to mount the filesystem with `fs-verity` authenticity protection, first enable `fs-verity` by running:
```
$ cargo run --release -- enable-fs-verity /tmp/puzzlefs-image puzzlefs_example 9ac9abc098870c55cc61431dae8635806273d8f61274d34bec062560e79dc2f5
$ cargo run --release -- enable-fs-verity /tmp/puzzlefs-image:puzzlefs_example 9ac9abc098870c55cc61431dae8635806273d8f61274d34bec062560e79dc2f5
```
This makes the data and metadata files readonly. Any reads of corrupted data will fail.

Expand Down
4 changes: 2 additions & 2 deletions exe/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "puzzlefs"
version = "0.1.0"
version = "0.2.0"
authors = ["Tycho Andersen <[email protected]>", "Ariel Miculas <[email protected]>"]
description = """
PuzzleFS is a next-generation container filesystem.
Expand All @@ -24,7 +24,7 @@ log = "0.4.17"
env_logger = "0.9.3"
syslog = "6.0.1"
os_pipe = "1.1.2"
puzzlefs-lib = { path = "../puzzlefs-lib", version = "0.1.0" }
puzzlefs-lib = { path = "../puzzlefs-lib", version = "0.2.0" }
hex = "0.4.3"

[dev-dependencies]
Expand Down
41 changes: 25 additions & 16 deletions exe/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ enum SubCommand {
struct Build {
rootfs: String,
oci_dir: String,
tag: String,
#[arg(short, long, value_name = "base-layer")]
base_layer: Option<String>,
#[arg(short, long, value_name = "compressed")]
Expand All @@ -48,7 +47,6 @@ struct Build {
#[derive(Args)]
struct Mount {
oci_dir: String,
tag: String,
mountpoint: String,
#[arg(short, long)]
foreground: bool,
Expand All @@ -63,14 +61,12 @@ struct Mount {
#[derive(Args)]
struct Extract {
oci_dir: String,
tag: String,
extract_dir: String,
}

#[derive(Args)]
struct FsVerity {
oci_dir: String,
tag: String,
root_hash: String,
}

Expand Down Expand Up @@ -148,32 +144,42 @@ fn mount_background(
Ok(())
}

fn parse_oci_dir(oci_dir: &str) -> anyhow::Result<(&str, &str)> {
let components: Vec<&str> = oci_dir.split_terminator(":").collect();
if components.len() != 2 {
anyhow::bail!("Expected oci_dir in the following format <oci_dir>:<tag> ")
}

Ok((components[0], components[1]))
}

fn main() -> anyhow::Result<()> {
let opts: Opts = Opts::parse();
match opts.subcmd {
SubCommand::Build(b) => {
let rootfs = Path::new(&b.rootfs);
let oci_dir = Path::new(&b.oci_dir);
let (oci_dir, tag) = parse_oci_dir(&b.oci_dir)?;
let oci_dir = Path::new(oci_dir);
let image = Image::new(oci_dir)?;
let new_image = match b.base_layer {
Some(base_layer) => {
let (_desc, image) = if b.compression {
add_rootfs_delta::<Zstd>(rootfs, image, &b.tag, &base_layer)?
add_rootfs_delta::<Zstd>(rootfs, image, tag, &base_layer)?
} else {
add_rootfs_delta::<Noop>(rootfs, image, &b.tag, &base_layer)?
add_rootfs_delta::<Noop>(rootfs, image, tag, &base_layer)?
};
image
}
None => {
if b.compression {
build_initial_rootfs::<Zstd>(rootfs, &image, &b.tag)?
build_initial_rootfs::<Zstd>(rootfs, &image, tag)?
} else {
build_initial_rootfs::<Noop>(rootfs, &image, &b.tag)?
build_initial_rootfs::<Noop>(rootfs, &image, tag)?
};
Arc::new(image)
}
};
let mut manifest_fd = new_image.get_image_manifest_fd(&b.tag)?;
let mut manifest_fd = new_image.get_image_manifest_fd(tag)?;
let mut read_buffer = Vec::new();
manifest_fd.read_to_end(&mut read_buffer)?;
let manifest_digest = get_fs_verity_digest(&read_buffer)?;
Expand All @@ -191,7 +197,8 @@ fn main() -> anyhow::Result<()> {
init_syslog(log_level)?;
}

let oci_dir = Path::new(&m.oci_dir);
let (oci_dir, tag) = parse_oci_dir(&m.oci_dir)?;
let oci_dir = Path::new(oci_dir);
let oci_dir = fs::canonicalize(oci_dir)?;
let image = Image::open(&oci_dir)?;
let mountpoint = Path::new(&m.mountpoint);
Expand All @@ -213,7 +220,7 @@ fn main() -> anyhow::Result<()> {
let named_pipe = m.init_pipe.map(PathBuf::from);
let result = spawn_mount(
image,
&m.tag,
tag,
&mountpoint,
&m.options.unwrap_or_default(),
named_pipe.clone().map(PipeDescriptor::NamedPipe),
Expand Down Expand Up @@ -244,7 +251,7 @@ fn main() -> anyhow::Result<()> {

if let Err(e) = mount_background(
image,
&m.tag,
tag,
&mountpoint,
m.options,
manifest_verity,
Expand All @@ -262,14 +269,16 @@ fn main() -> anyhow::Result<()> {
Ok(())
}
SubCommand::Extract(e) => {
let (oci_dir, tag) = parse_oci_dir(&e.oci_dir)?;
init_logging("info");
extract_rootfs(&e.oci_dir, &e.tag, &e.extract_dir)
extract_rootfs(oci_dir, tag, &e.extract_dir)
}
SubCommand::EnableFsVerity(v) => {
let oci_dir = Path::new(&v.oci_dir);
let (oci_dir, tag) = parse_oci_dir(&v.oci_dir)?;
let oci_dir = Path::new(oci_dir);
let oci_dir = fs::canonicalize(oci_dir)?;
let image = Image::open(&oci_dir)?;
enable_fs_verity(image, &v.tag, &v.root_hash)?;
enable_fs_verity(image, tag, &v.root_hash)?;
Ok(())
}
}
Expand Down
8 changes: 4 additions & 4 deletions exe/tests/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ fn build_and_extract_is_noop() -> anyhow::Result<()> {

// TODO: figure out a better way to do all this osstr stuff...
let oci = dir.path().join("oci");
let mut oci_arg = oci.into_os_string();
oci_arg.push(OsStr::new(":test"));
puzzlefs([
OsStr::new("build"),
ubuntu_rootfs.as_ref(),
oci.as_ref(),
OsStr::new("test"),
oci_arg.as_ref(),
])?;

let extracted = dir.path().join("extracted");
puzzlefs([
OsStr::new("extract"),
oci.as_os_str(),
OsStr::new("test"),
oci_arg.as_os_str(),
extracted.as_os_str(),
])?;
assert!(!dir_diff::is_different(ubuntu_rootfs, extracted).unwrap());
Expand Down
18 changes: 6 additions & 12 deletions exe/tests/verity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,9 @@ fn test_fs_verity() -> anyhow::Result<()> {
let rootfs = Path::new("../puzzlefs-lib/src/builder/test/test-1/");

let oci = mount_path.join("oci");
let output = puzzlefs([
OsStr::new("build"),
rootfs.as_ref(),
oci.as_ref(),
OsStr::new("test"),
])?;
let mut oci_arg = oci.clone().into_os_string();
oci_arg.push(OsStr::new(":test"));
let output = puzzlefs([OsStr::new("build"), rootfs.as_ref(), oci_arg.as_ref()])?;

let tokens = output.split_whitespace().collect::<Vec<_>>();

Expand All @@ -101,8 +98,7 @@ fn test_fs_verity() -> anyhow::Result<()> {

puzzlefs([
OsStr::new("enable-fs-verity"),
oci.as_ref(),
OsStr::new("test"),
oci_arg.as_ref(),
OsStr::new(digest),
])?;

Expand All @@ -118,8 +114,7 @@ fn test_fs_verity() -> anyhow::Result<()> {
OsStr::new("-f"),
OsStr::new("-d"),
OsStr::new(RANDOM_DIGEST),
oci.as_ref(),
OsStr::new("test"),
oci_arg.as_ref(),
OsStr::new(&puzzlefs_mountpoint),
]);

Expand All @@ -133,8 +128,7 @@ fn test_fs_verity() -> anyhow::Result<()> {
OsStr::new("mount"),
OsStr::new("-d"),
OsStr::new(digest),
oci.as_ref(),
OsStr::new("test"),
oci_arg.as_ref(),
OsStr::new(&puzzlefs_mountpoint),
])?;

Expand Down
2 changes: 1 addition & 1 deletion puzzlefs-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "puzzlefs-lib"
version = "0.1.0"
version = "0.2.0"
authors = ["Tycho Andersen <[email protected]>", "Ariel Miculas <[email protected]>"]
description = """
Build, mount and extract PuzzleFS images.
Expand Down
46 changes: 27 additions & 19 deletions puzzlefs-lib/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::fsverity_helpers::{
};
use crate::oci::Digest;
use std::any::Any;
use std::backtrace::Backtrace;
use std::cmp::min;
use std::collections::{BTreeMap, HashMap};
use std::ffi::{OsStr, OsString};
Expand Down Expand Up @@ -399,7 +400,7 @@ pub fn build_initial_rootfs<C: Compression + Any>(
tag: &str,
) -> Result<Descriptor> {
let mut verity_data: VerityData = BTreeMap::new();
let mut image_manifest = Image::get_empty_manifest()?;
let mut image_manifest = oci.get_empty_manifest()?;
let inodes = build_delta::<C>(rootfs, oci, None, &mut verity_data, &mut image_manifest)?;

let rootfs_buf = serialize_metadata(Rootfs {
Expand Down Expand Up @@ -430,7 +431,7 @@ pub fn add_rootfs_delta<C: Compression + Any>(
base_layer: &str,
) -> Result<(Descriptor, Arc<Image>)> {
let mut verity_data: VerityData = BTreeMap::new();
let mut image_manifest = Image::get_empty_manifest()?;
let mut image_manifest = oci.get_empty_manifest()?;

let pfs = PuzzleFS::open(oci, base_layer, None)?;
let oci = Arc::clone(&pfs.oci);
Expand Down Expand Up @@ -462,11 +463,9 @@ pub fn add_rootfs_delta<C: Compression + Any>(
Ok((rootfs_descriptor, oci))
}

pub fn enable_fs_verity(oci: Image, tag: &str, manifest_root_hash: &str) -> Result<()> {
// first enable fs verity for the puzzlefs image manifest
let manifest_fd = oci.get_image_manifest_fd(tag)?;
fn enable_verity_for_file(file: &cap_std::fs::File) -> Result<()> {
if let Err(e) = fsverity_enable(
manifest_fd.as_raw_fd(),
file.as_raw_fd(),
FS_VERITY_BLOCK_SIZE_DEFAULT,
InnerHashAlgorithm::Sha256,
&[],
Expand All @@ -476,26 +475,35 @@ pub fn enable_fs_verity(oci: Image, tag: &str, manifest_root_hash: &str) -> Resu
return Err(WireFormatError::from(e));
}
}
check_fs_verity(&manifest_fd, &hex::decode(manifest_root_hash)?[..])?;
Ok(())
}

fn enable_and_check_verity_for_file(file: &cap_std::fs::File, expected: &[u8]) -> Result<()> {
enable_verity_for_file(file)?;
check_fs_verity(file, expected)
}

pub fn enable_fs_verity(oci: Image, tag: &str, manifest_root_hash: &str) -> Result<()> {
// first enable fs verity for the puzzlefs image manifest
let manifest_fd = oci.get_image_manifest_fd(tag)?;
enable_and_check_verity_for_file(&manifest_fd, &hex::decode(manifest_root_hash)?[..])?;

let pfs = PuzzleFS::open(oci, tag, None)?;
let oci = Arc::clone(&pfs.oci);
let rootfs = oci.open_rootfs_blob(tag, None)?;

let rootfs_fd = oci.get_pfs_rootfs(tag, None)?;
if let Err(e) = fsverity_enable(
rootfs_fd.as_raw_fd(),
FS_VERITY_BLOCK_SIZE_DEFAULT,
InnerHashAlgorithm::Sha256,
&[],
) {
// if fsverity is enabled, ignore the error
if e.kind() != std::io::ErrorKind::AlreadyExists {
return Err(WireFormatError::from(e));
}
}
let rootfs_verity = oci.get_pfs_rootfs_verity(tag)?;
check_fs_verity(&rootfs_fd, &rootfs_verity[..])?;

enable_and_check_verity_for_file(&rootfs_fd, &rootfs_verity[..])?;

let manifest = oci
.0
.find_manifest_with_tag(tag)?
.ok_or_else(|| WireFormatError::MissingManifest(tag.to_string(), Backtrace::capture()))?;
let config_digest = manifest.config().digest().digest();
let config_digest_path = oci.blob_path().join(config_digest);
enable_verity_for_file(&oci.0.dir.open(config_digest_path)?)?;

for (content_addressed_file, verity_hash) in rootfs.get_verity_data()? {
let file_path = oci
Expand Down
Loading

0 comments on commit 4afc76a

Please sign in to comment.