Skip to content

Commit

Permalink
enclave_build: define and implement image manager trait
Browse files Browse the repository at this point in the history
For future generalization of both Docker and OCI methods of build, the
`ImageManager` trait will be used to extract useful information from the
container image details.

Currently added the OCI implementation of the trait and it can be used
to pull, inspect and extract expressions from  an image just like the Docker
case. The same implementation will be added for Docker images.

enclave_build: add unit test for image manager

Added unit tests for image manager operations.

Signed-off-by: Calin-Alexandru Coman <[email protected]>
Signed-off-by: Raul-Ovidiu Moldovan <[email protected]>
  • Loading branch information
raulmldv committed Apr 5, 2023
1 parent 3dad5a0 commit 5476ff4
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 20 deletions.
2 changes: 1 addition & 1 deletion enclave_build/src/docker.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright 2019-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use base64::{engine::general_purpose, Engine as _};
use crate::{EnclaveBuildError, Result};
use base64::{engine::general_purpose, Engine as _};
use futures::stream::StreamExt;
use log::{debug, info};
use serde_json::{json, Value};
Expand Down
35 changes: 35 additions & 0 deletions enclave_build/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::{EnclaveBuildError, Result};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::io::Read;
use tempfile::NamedTempFile;

use sha2::Digest;

Expand Down Expand Up @@ -53,6 +54,40 @@ impl ImageDetails {
pub fn config(&self) -> &ImageConfiguration {
&self.config
}

/// Extracts from the image and returns the CMD and ENV expressions (in this order).
///
/// If there are no CMD expressions found, it tries to locate the ENTRYPOINT command.
/// The returned result will contain the files needed for the YAML generator.
pub fn extract_expressions(&self) -> Result<(NamedTempFile, NamedTempFile)> {
// Get the expressions from the image
let config_section = self
.config
.config()
.as_ref()
.ok_or(EnclaveBuildError::ConfigError)?;

let cmd = config_section.cmd();
let env = config_section.env();
let entrypoint = config_section.entrypoint();

// If no CMD instructions are found, try to locate an ENTRYPOINT command
let (cmd, env) = match (cmd, env, entrypoint) {
(Some(cmd), Some(env), _) => Ok((cmd.to_vec(), env.to_vec())),
(_, Some(env), Some(entrypoint)) => Ok((entrypoint.to_vec(), env.to_vec())),
(_, _, Some(entrypoint)) => Ok((entrypoint.to_vec(), Vec::new())),
(_, _, _) => Err(EnclaveBuildError::ExtractError(
"Failed to locate ENTRYPOINT".to_string(),
)),
}?;

let cmd_file = crate::docker::write_config(cmd)
.map_err(|err| EnclaveBuildError::ExtractError(format!("{:?}", err)))?;
let env_file = crate::docker::write_config(env)
.map_err(|err| EnclaveBuildError::ExtractError(format!("{:?}", err)))?;

Ok((cmd_file, env_file))
}
}

/// URIs that are missing a domain will be converted to a reference using the Docker defaults.
Expand Down
105 changes: 87 additions & 18 deletions enclave_build/src/image_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@

use std::convert::TryFrom;

use log::warn;
use oci_distribution::Reference;
use tempfile::NamedTempFile;

use crate::image::ImageDetails;
use crate::storage::OciStorage;
use crate::{EnclaveBuildError, Result};

/// Trait which provides an interface for handling an image (OCI or Docker)
pub trait ImageManager {
fn image_name(&self) -> &str;
/// Inspects the image and returns its metadata in the form of a JSON Value
fn inspect_image(&self) -> Result<serde_json::Value>;
/// Returns the architecture of the image
fn architecture(&self) -> Result<String>;
/// Returns two temp files containing the CMD and ENV expressions extracted from the image
fn extract_expressions(&self) -> Result<(NamedTempFile, NamedTempFile)>;
}

pub struct OciImageManager {
/// Name of the container image.
image_name: String,
Expand All @@ -25,16 +38,15 @@ impl OciImageManager {
let image_name = normalize_tag(image_name)?;

// The docker daemon is not used, so a local storage needs to be created
let storage =
match OciStorage::get_default_root_path().map_err(|err| eprintln!("{:?}", err)) {
Ok(root_path) => {
// Try to create/read the storage. If the storage could not be created, log the error
OciStorage::new(&root_path)
.map_err(|err| eprintln!("{:?}", err))
.ok()
}
Err(_) => None,
};
let storage = match OciStorage::get_default_root_path().map_err(|err| warn!("{:?}", err)) {
Ok(root_path) => {
// Try to create/read the storage. If the storage could not be created, log the error
OciStorage::new(&root_path)
.map_err(|err| warn!("{:?}", err))
.ok()
}
Err(_) => None,
};

let image_details = Self::fetch_image_details(&image_name, storage).await?;

Expand All @@ -60,11 +72,7 @@ impl OciImageManager {

let image_details = if let Some(storage) = local_storage {
// Try to fetch the image from the storage
storage.fetch_image_details(image_name).map_err(|err| {
// Log the fetching error
eprintln!("{:?}", err);
err
})
storage.fetch_image_details(image_name)
} else {
Err(EnclaveBuildError::OciStorageNotFound(
"Local storage missing".to_string(),
Expand All @@ -74,15 +82,16 @@ impl OciImageManager {
// If the fetching failed, pull it from remote and store it
match image_details {
Ok(details) => Ok(details),
Err(_) => {
Err(err) => {
warn!("Fetching from storage failed: {}", err);
// The image is not stored, so try to pull and then store it
let image_data = crate::pull::pull_image_data(image_name).await?;

// If the store operation fails, discard error and proceed with getting the details
if let Some(local_storage) = storage.as_mut() {
local_storage
.store_image_data(image_name, &image_data)
.map_err(|err| eprintln!("Failed to store image: {:?}", err))
.map_err(|err| warn!("Failed to store image: {:?}", err))
.ok();
}

Expand All @@ -105,8 +114,35 @@ fn normalize_tag(image_name: &str) -> Result<String> {
}
}

impl ImageManager for OciImageManager {
fn image_name(&self) -> &str {
&self.image_name
}

/// Inspect the image and return its description as a JSON String.
fn inspect_image(&self) -> Result<serde_json::Value> {
// Serialize to a serde_json::Value
serde_json::to_value(&self.image_details).map_err(EnclaveBuildError::SerdeError)
}

/// Extracts the CMD and ENV expressions from the image and returns them each in a
/// temporary file
fn extract_expressions(&self) -> Result<(NamedTempFile, NamedTempFile)> {
self.image_details.extract_expressions()
}

/// Returns architecture information of the image.
fn architecture(&self) -> Result<String> {
Ok(format!("{}", self.image_details.config().architecture()))
}
}

#[cfg(test)]
pub mod tests {
mod tests {
use super::*;
use std::fs::File;
use std::io::Read;

use sha2::Digest;

use super::{normalize_tag, OciImageManager};
Expand Down Expand Up @@ -177,4 +213,37 @@ pub mod tests {

assert_eq!(&config_hash, IMAGE_HASH);
}

/// Test extracted configuration is as expected
#[tokio::test]
async fn test_config() {
#[cfg(target_arch = "x86_64")]
let image_manager = OciImageManager::new(
"667861386598.dkr.ecr.us-east-1.amazonaws.com/enclaves-samples:vsock-sample-server-x86_64",
).await.unwrap();
#[cfg(target_arch = "aarch64")]
let mut image_manager = OciImageManager::new(
"667861386598.dkr.ecr.us-east-1.amazonaws.com/enclaves-samples:vsock-sample-server-aarch64",
).await.unwrap();

let (cmd_file, env_file) = image_manager.extract_expressions().unwrap();
let mut cmd_file = File::open(cmd_file.path()).unwrap();
let mut env_file = File::open(env_file.path()).unwrap();

let mut cmd = String::new();
cmd_file.read_to_string(&mut cmd).unwrap();
assert_eq!(
cmd,
"/bin/sh\n\
-c\n\
./vsock-sample server --port 5005\n"
);

let mut env = String::new();
env_file.read_to_string(&mut env).unwrap();
assert_eq!(
env,
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n"
);
}
}
1 change: 0 additions & 1 deletion enclave_build/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ impl OciStorage {
err
))
})?
.into_iter()
// Get only the valid directory entries that are valid files and return (name, file) pair
.filter_map(|entry| match entry {
Ok(dir_entry) => match File::open(dir_entry.path()) {
Expand Down

0 comments on commit 5476ff4

Please sign in to comment.