Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement image-rs basic features #4

Merged
merged 12 commits into from
Feb 21, 2022
Merged

Implement image-rs basic features #4

merged 12 commits into from
Feb 21, 2022

Conversation

arronwy
Copy link
Member

@arronwy arronwy commented Jan 19, 2022

This PR implements image-rs basic features including OCI image download/decryption/decompression/unpack/mount.

Image signing verification is not included.

@jiangliu
Copy link
Member

jiangliu commented Jan 21, 2022

As we are early in the development stages, let's merge the PR so others may contribute to it? @sameo

Copy link
Member

@stevenhorsman stevenhorsman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to check out the code and run the tests but hit some problems and had some questions.

Comment on lines +17 to +19
oci-distribution = { git = "https://github.com/arronwy/oci-distribution", branch = "export_pull_layer" }
oci-spec = { git = "https://github.com/containers/oci-spec-rs" }
ocicrypt-rs = { git = "https://github.com/arronwy/ocicrypt-rs", branch = "oci_distribution" }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @arronwy - I notice that these dependencies are pulled from a branch of your fork of the projects, is that something you're planning to resolve soon by getting those changes merged upstream and then using the main versions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there are two pending PR in oci-distribution to support manifest list and public pull layer API, after these two PRs are merged, we'll use the upstream repo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any progress about the oci-distribution upstream?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream PR for image manifest list support have beed merged, they basicly agree public pull_layer and auth API, the PR should be merged soon, these two API may face refactoring when oci-distribution targeting 1.0 release.

// SPDX-License-Identifier: Apache-2.0

use anyhow::{anyhow, Result};
use nix::mount::MsFlags;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I run the cargo build on my mac I get:

unresolved import `nix::mount`
 --> src/snapshots/overlay.rs:6:10
  |
6 | use nix::mount::MsFlags;
  |          ^^^^^ could not find `mount` in `nix`

It may well be that mac (x86_64, not darwin) isn't supported, but I thought I'd let you know.

src/decrypt.rs Show resolved Hide resolved
@@ -0,0 +1,16 @@
## Generate keypair and container image for unit and integration tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be worth adding some more instructions here, for example getting the user to log into their container registry then set a shell variable to their user account. e.g. export cr_user="<your container reg account>
then updating the skopeo commands to replace user with cr_user.
I'm not sure if this is supposed to support more that just Docker Hub either?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can add more detail info for the setup. It should support different registries, I tried with a local registry and Docker Hub, encrypted container image both works, but the server side may change the dest-compress-format.

@@ -0,0 +1,16 @@
## Generate keypair and container image for unit and integration tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it is worth linking this file on the main README.md as part of a set up guide as it's easy to miss?

@arronwy
Copy link
Member Author

arronwy commented Jan 21, 2022

I tried to check out the code and run the tests but hit some problems and had some questions.

Hi @stevenhorsman, Thanks for your try, I just added the keyprovider config to the repo and the CI passed:
https://github.com/arronwy/image-rs/actions/runs/1729173936

BTW for test_pull_image since the final mount operation need root privilege, we need test with sudo command.

@stevenhorsman
Copy link
Member

I tried to check out the code and run the tests but hit some problems and had some questions.

Hi @stevenhorsman, Thanks for your try, I just added the keyprovider config to the repo and the CI passed: https://github.com/arronwy/image-rs/actions/runs/1729173936

BTW for test_pull_image since the final mount operation need root privilege, we need test with sudo command.

Thanks for the info - and adding the GHA CI output link. I'm seeing better behaviour locally now, but still not quite right.
If I run the test_pull_image on it's own it works:

sudo -E PATH=$PATH -s cargo test image::tests::test_pull_image
    Finished test [unoptimized + debuginfo] target(s) in 0.19s
     Running unittests (/root/go/src/github.com/confidential-containers/image-rs/target/debug/deps/image_rs-9b1dcdc702e8e38c)

running 1 test
test image::tests::test_pull_image ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 16.18s

but if I run all the tests it fails (differently from before):

sudo -E PATH=$PATH -s cargo test
    Finished test [unoptimized + debuginfo] target(s) in 0.18s
     Running unittests (/root/go/src/github.com/confidential-containers/image-rs/target/debug/deps/image_rs-9b1dcdc702e8e38c)

running 9 tests
test config::tests::test_image_config ... ok
test decoder::tests::test_uncompressed_decode ... ok
test config::tests::test_image_config_from_file ... ok
test decoder::tests::test_zstd_decode ... ok
test decoder::tests::test_gzip_decode ... ok
test bundle::tests::test_bundle_create_config ... ok
test unpack::tests::test_unpack ... ok
test pull::tests::test_pull_client ... ok
test image::tests::test_pull_image ... FAILED

failures:

---- image::tests::test_pull_image stdout ----
thread 'image::tests::test_pull_image' panicked at 'assertion failed: image_client.pull_image(image, bundle_dir.path(), &None, &None).await.is_ok()', src/image.rs:222:13

I tried single threading the tests to see if that helped, but it still failed, so it doesn't look like it was a synchronous issue.

It is super late for you, so I'm not expecting anything now, I just wanted to share what I saw. Thanks!

@jiangliu
Copy link
Member

I tried to check out the code and run the tests but hit some problems and had some questions.

Hi @stevenhorsman, Thanks for your try, I just added the keyprovider config to the repo and the CI passed: https://github.com/arronwy/image-rs/actions/runs/1729173936
BTW for test_pull_image since the final mount operation need root privilege, we need test with sudo command.

Thanks for the info - and adding the GHA CI output link. I'm seeing better behaviour locally now, but still not quite right. If I run the test_pull_image on it's own it works:

sudo -E PATH=$PATH -s cargo test image::tests::test_pull_image
    Finished test [unoptimized + debuginfo] target(s) in 0.19s
     Running unittests (/root/go/src/github.com/confidential-containers/image-rs/target/debug/deps/image_rs-9b1dcdc702e8e38c)

running 1 test
test image::tests::test_pull_image ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 16.18s

but if I run all the tests it fails (differently from before):

sudo -E PATH=$PATH -s cargo test
    Finished test [unoptimized + debuginfo] target(s) in 0.18s
     Running unittests (/root/go/src/github.com/confidential-containers/image-rs/target/debug/deps/image_rs-9b1dcdc702e8e38c)

running 9 tests
test config::tests::test_image_config ... ok
test decoder::tests::test_uncompressed_decode ... ok
test config::tests::test_image_config_from_file ... ok
test decoder::tests::test_zstd_decode ... ok
test decoder::tests::test_gzip_decode ... ok
test bundle::tests::test_bundle_create_config ... ok
test unpack::tests::test_unpack ... ok
test pull::tests::test_pull_client ... ok
test image::tests::test_pull_image ... FAILED

failures:

---- image::tests::test_pull_image stdout ----
thread 'image::tests::test_pull_image' panicked at 'assertion failed: image_client.pull_image(image, bundle_dir.path(), &None, &None).await.is_ok()', src/image.rs:222:13

I tried single threading the tests to see if that helped, but it still failed, so it doesn't look like it was a synchronous issue.

It is super late for you, so I'm not expecting anything now, I just wanted to share what I saw. Thanks!

Most code is still assuming Linux. We may need a plan to support other OS .

@stevenhorsman
Copy link
Member

Most code is still assuming Linux. We may need a plan to support other OS .

Sorry, I wasn't clear - this result in on my Ubuntu 20.04 system, not macOS

@arronwy
Copy link
Member Author

arronwy commented Jan 22, 2022

I tried single threading the tests to see if that helped, but it still failed, so it doesn't look like it was a synchronous issue.
It is super late for you, so I'm not expecting anything now, I just wanted to share what I saw. Thanks!

Most code is still assuming Linux. We may need a plan to support other OS .

Yes, we can add other OS support to the TODO list and add this info to the README

@arronwy
Copy link
Member Author

arronwy commented Jan 22, 2022

Yes, we can add other OS support to the TODO list and add this info to the README

Most code is still assuming Linux. We may need a plan to support other OS .

Sorry, I wasn't clear - this result in on my Ubuntu 20.04 system, not macOS

The root cause maybe image-rs store the layers data to default work dir, during pull image it will check whether image layers data already downloaded, and remove_image API is not implemented yet:

const DEFAULT_WORK_DIR: &str = "/var/lib/image-rs/";

I updated to use tempdir for the work dir, it works with multiple times cargo test now.

@jiangliu
Copy link
Member

Yes, we can add other OS support to the TODO list and add this info to the README

Most code is still assuming Linux. We may need a plan to support other OS .

Sorry, I wasn't clear - this result in on my Ubuntu 20.04 system, not macOS

The root cause maybe image-rs store the layers data to default work dir, during pull image it will check whether image layers data already downloaded, and remove_image API is not implemented yet:

const DEFAULT_WORK_DIR: &str = "/var/lib/image-rs/";

I updated to use tempdir for the work dir, it works with multiple times cargo test now.

It would be better to use tempfile crate to create tmpdir for tests, but be careful that the downloaded image may cost too much memory when tmpfs is used.

@arronwy
Copy link
Member Author

arronwy commented Jan 22, 2022

The root cause maybe image-rs store the layers data to default work dir, during pull image it will check whether image layers data already downloaded, and remove_image API is not implemented yet:

const DEFAULT_WORK_DIR: &str = "/var/lib/image-rs/";

I updated to use tempdir for the work dir, it works with multiple times cargo test now.

It would be better to use tempfile crate to create tmpdir for tests, but be careful that the downloaded image may cost too much memory when tmpfs is used.

Yes, we use tempfile crate for all file related tests now. Thanks for the reminder, we use small size container image like busybox for current test cases. The following benchmark test will use bigger container image, we'll ensure it works with current CI.

@stevenhorsman
Copy link
Member

Yes, we use tempfile crate for all file related tests now. Thanks for the reminder, we use small size container image like busybox for current test cases. The following benchmark test will use bigger container image, we'll ensure it works with current CI.

Thanks for the changed. Just to confirm that I can run sudo -E PATH=$PATH -s cargo test multiple times now and the tests all pass! 👍

src/snapshots/overlay.rs Outdated Show resolved Hide resolved
src/unpack.rs Show resolved Hide resolved
Copy link
Member

@fitzthum fitzthum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comments. Looks good overall.

src/pull.rs Outdated
let mut media_type_str: &str = layer.media_type.as_str();

let mut decryptor = Decryptor::default();
if decryptor.is_encrypted(&layer.media_type) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the issues we ran into with skopeo was that if a decryption key was provided but the image was not encrypted, the image would be unpacked anyway. For CCv0 this meant that if the CSP intercepted an encrypted image and replaced it with an unencrypted image, skopeo would unpack the unencrypted image inside the enclave without warning the user.

Does image-rs do something similar? Here it looks like the format of the image determines whether the image is decrypted or not. Do we check whether or not encryption is expected from by the client?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, current PR for image-rs do the same way as skopeo, and after we pulled the image, we will return the image and layer metadata info like whether the image is signed and layer is encrypted.

The next step work for integrating signature crate which also provide the policy info, we will do these verifications to ensure the image/layer info is comply with the policy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's just make sure we enforce the guest owner's policy at some point, even for unsigned images.

data/ocicrypt_keyprovider.conf Outdated Show resolved Hide resolved
Copy link
Member

@dubek dubek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small comment:

data/private_key.pem Outdated Show resolved Hide resolved
Copy link
Member

@jodh-intel jodh-intel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @arronwy. A few comments...

command: build
args: --all-features

- name: Run cargo test with root privilege
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is root needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The snapshots module will do mount operation which require the root privilege.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a comment to make that clear. Also, is it worth running the unit tests twice as we do in Kata, once as root and once as non-root? That would allow you to assert that the mount tests (correctly) failed when not running as root for example.

src/bundle.rs Outdated Show resolved Hide resolved
#[derive(Clone, Debug, Deserialize)]
pub struct ImageConfig {
/// The location for `image-rs` to store data.
pub work_dir: PathBuf,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Should this path exist or will it be created?
  • Should it be an absolute path or can it be relative?
  • Are sym links allowed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

work_dir will be created if not existed, it can be relative or sym links, we did not enforce any policy or check for this directory.

src/config.rs Outdated
// Construct a default instance of `ImageConfig`
fn default() -> ImageConfig {
let work_dir = PathBuf::from(
std::env::var("IMAGE_WORK_DIR").unwrap_or_else(|_| DEFAULT_WORK_DIR.to_string()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Why is this needed / who uses this variable? Is it for testing / debug only?
  • This looks potentially dangerous as no validation is being done on this env var value.
  • The name of the variable is rather generic. If this is really needed what about prefixing with CC_ or CONFIDENTIAL_CONTAINERS_?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable allow the user to easy set the work dir without create the config file.
Prefix with CC_ will be much better.

src/config.rs Outdated Show resolved Hide resolved
src/unpack.rs Show resolved Hide resolved
Copy link
Member

@sameo sameo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of my comments are minor. This PR is very well laid out and easy to review, thanks a lot
@arronwy.

One overall comment: Could you please add some more detailed commit messages?

src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/decoder/mod.rs Outdated Show resolved Hide resolved
src/unpack.rs Show resolved Hide resolved
src/decrypt.rs Outdated Show resolved Hide resolved
src/meta_store.rs Outdated Show resolved Hide resolved
.map_err(|e| anyhow!("failed to open metastore file {}", e.to_string()))?;
serde_json::from_reader::<File, MetaStore>(file)
.map_err(|e| anyhow!("failed to parse metastore file {}", e.to_string()))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we store/serialize the store?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be in to todo list since current API only cover pull_image API.

src/image.rs Outdated Show resolved Hide resolved
image: &str,
bundle_dir: &Path,
auth_info: &Option<&str>,
decrypt_config: &Option<&str>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here again, this unstructured decrypt_config argument is very opaque. What kind of strings are we expecting here?

src/image.rs Show resolved Hide resolved
Signed-off-by: Samuel Ortiz <[email protected]>
Copy link
Member Author

@arronwy arronwy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @sameo @jodh-intel for your reviewing. I'll add more detailed commit message and resolve all your comments.

command: build
args: --all-features

- name: Run cargo test with root privilege
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The snapshots module will do mount operation which require the root privilege.

src/bundle.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
src/unpack.rs Show resolved Hide resolved
src/unpack.rs Show resolved Hide resolved
src/snapshots/mod.rs Outdated Show resolved Hide resolved
src/image.rs Outdated Show resolved Hide resolved
src/decrypt.rs Outdated Show resolved Hide resolved
use tempfile;

#[test]
fn test_unpack() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the comments about the current limitations of tar-rs, shouldn't this also test sym-links? Also, what about other file types like char+block devices? I'm just wondering if we need tests for those here or can we rely on tar-rs for those tests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason we did not test sym-links is current tar-rs crate packing feature will not preserve the file time info, we may need a sepeate tool to crate the test file, I have add it as TODO for sym-links and other file types.

Current support gzip and zstd compression algorithm type.

Signed-off-by: Arron Wang <[email protected]>
Unpack module will unpack contents of tarball to the destination path.

Signed-off-by: Arron Wang <[email protected]>
src/decrypt.rs Outdated Show resolved Hide resolved
@sameo
Copy link
Member

sameo commented Feb 16, 2022

@jodh-intel @stevenhorsman If you're ok with that new version, I think we could merge it.

The decrypt module will use https://github.com/containers/ocicrypt-rs
project to parse/retrive decrypt key and decrypt the data.

Signed-off-by: Arron Wang <[email protected]>
Copy link
Member

@jodh-intel jodh-intel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @arronwy. A few nits and a small bug (I think)...

src/bundle.rs Outdated Show resolved Hide resolved
src/bundle.rs Show resolved Hide resolved
src/bundle.rs Show resolved Hide resolved
src/config.rs Outdated Show resolved Hide resolved
test_data/generate_test_data.md Outdated Show resolved Hide resolved
Provide the API to create bundle runtime config file which mainly
add the config from container images:
https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/config.go

to the annotation part of container runtime spec:
https://github.com/opencontainers/runtime-spec/blob/main/specs-go/config.go

Signed-off-by: Arron Wang <[email protected]>
Current we implement overlay snapshot as the default type.

Signed-off-by: Arron Wang <[email protected]>
pull module will export the API to pull container image manifest, config and layers.
it utilize oci-distribution crate to handle the real download process.

Signed-off-by: Arron Wang <[email protected]>
The image module will provide CRI image service compatiable API,
currently we implemented pull_image API.

The unit test will focuse on different container registries to
ensure the compalitibility with remote registries.

Signed-off-by: Arron Wang <[email protected]>
Currently we use a hashmap to store the container image related
metadata info, we may switch to a light database if needed.

Signed-off-by: Arron Wang <[email protected]>
Copy link
Member

@jodh-intel jodh-intel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @arronwy.

lgtm

@sameo sameo merged commit 8dc4d9d into confidential-containers:main Feb 21, 2022
@arronwy arronwy deleted the basic_features branch April 29, 2022 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants