diff --git a/enclave_build/Cargo.toml b/enclave_build/Cargo.toml index a38eee3df..e991a49f3 100644 --- a/enclave_build/Cargo.toml +++ b/enclave_build/Cargo.toml @@ -13,8 +13,6 @@ clap = "3.2" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" serde_json = "1.0" -shiplift = "0.7" -socket2 = { version = "0.4", features = ["all"] } tempfile = "3.5.0" tokio = { version = "1.27", features = ["rt-multi-thread"] } base64 = "0.21" @@ -24,3 +22,5 @@ sha2 = "0.9.5" futures = "0.3.28" aws-nitro-enclaves-image-format = "0.2" +tar = "0.4.40" +flate2 = "1.0.28" diff --git a/enclave_build/src/docker.rs b/enclave_build/src/docker.rs index 6b760bd7a..c230c3add 100644 --- a/enclave_build/src/docker.rs +++ b/enclave_build/src/docker.rs @@ -3,12 +3,13 @@ use crate::docker::DockerError::CredentialsError; use base64::{engine::general_purpose, Engine as _}; -use bollard::errors::Error; +use bollard::auth::DockerCredentials; +use bollard::image::{BuildImageOptions, CreateImageOptions}; +use bollard::Docker; +use flate2::{write::GzEncoder, Compression}; use futures::stream::StreamExt; use log::{debug, error, info}; use serde_json::{json, Value}; -use shiplift::RegistryAuth; -use shiplift::{BuildOptions, Docker, PullOptions}; use std::fs::File; use std::io::Write; use std::path::Path; @@ -22,6 +23,7 @@ pub const DOCKER_ARCH_AMD64: &str = "amd64"; #[derive(Debug, PartialEq, Eq)] pub enum DockerError { + ConnectionError, BuildError, InspectError, PullError, @@ -34,28 +36,30 @@ pub enum DockerError { /// Struct exposing the Docker functionalities to the EIF builder pub struct DockerUtil { docker: Docker, - docker2: bollard::Docker, docker_image: String, } impl DockerUtil { /// Constructor that takes as argument a tag for the docker image to be used - pub fn new(docker_image: String) -> Self { + pub fn new(docker_image: String) -> Result { let mut docker_image = docker_image; if !docker_image.contains(':') { docker_image.push_str(":latest"); } - DockerUtil { - // DOCKER_HOST environment variable is parsed inside - // if docker daemon address needs to be substituted. - // By default it tries to connect to 'unix:///var/run/docker.sock' - docker: Docker::new(), - docker2: bollard::Docker::connect_with_socket_defaults() - .expect("Failed to connect to Docker daemon"), + // DOCKER_HOST environment variable is parsed inside + // if docker daemon address needs to be substituted. + // By default, it tries to connect to 'unix:///var/run/docker.sock' + let docker = Docker::connect_with_defaults().map_err(|e| { + error!("{:?}", e); + DockerError::ConnectionError + })?; + + Ok(DockerUtil { + docker, docker_image, - } + }) } /// Returns the credentials by reading ${HOME}/.docker/config.json or ${DOCKER_CONFIG} @@ -64,7 +68,7 @@ impl DockerUtil { /// we are parsing it correctly, so the parsing mechanism had been infered by /// reading a config.json created by: // Docker version 19.03.2 - fn get_credentials(&self) -> Result { + fn get_credentials(&self) -> Result { let image = self.docker_image.clone(); let host = if let Ok(uri) = Url::parse(&image) { uri.host().map(|s| s.to_string()) @@ -113,10 +117,11 @@ impl DockerUtil { if let Some(index) = decoded.rfind(':') { let (user, after_user) = decoded.split_at(index); let (_, password) = after_user.split_at(1); - return Ok(RegistryAuth::builder() - .username(user) - .password(password) - .build()); + return Ok(DockerCredentials { + username: Some(user.to_string()), + password: Some(password.to_string()), + ..Default::default() + }); } } } @@ -185,38 +190,40 @@ impl DockerUtil { let act = async { // Check if the Docker image is locally available. // If available, early exit. - if self.image_exists().await.is_ok() { + if self.docker.inspect_image(&self.docker_image).await.is_ok() { eprintln!("Using the locally available Docker image..."); return Ok(()); } - let mut pull_options_builder = PullOptions::builder(); - pull_options_builder.image(&self.docker_image); - match self.get_credentials() { - Ok(auth) => { - pull_options_builder.auth(auth); - } + let create_image_options = CreateImageOptions { + from_image: self.docker_image.clone(), + ..Default::default() + }; + + let credentials = match self.get_credentials() { + Ok(auth) => Some(auth), // It is not mandatory to have the credentials set, but this is // the most likely reason for failure when pulling, so log the // error. Err(err) => { debug!("WARNING!! Credential could not be set {:?}", err); + None } }; - let mut stream = self.docker.images().pull(&pull_options_builder.build()); + let mut stream = + self.docker + .create_image(Some(create_image_options), None, credentials); loop { if let Some(item) = stream.next().await { match item { Ok(output) => { - let msg = &output; - - if let Some(err_msg) = msg.get("error") { - error!("{:?}", err_msg.clone()); + if let Some(err_msg) = &output.error { + error!("{:?}", err_msg); break Err(DockerError::PullError); } else { - info!("{}", msg); + info!("{:?}", output); } } Err(e) => { @@ -238,24 +245,36 @@ impl DockerUtil { /// Build an image locally, with the tag provided in constructor, using a /// directory that contains a Dockerfile pub fn build_image(&self, dockerfile_dir: String) -> Result<(), DockerError> { - let act = async { - let mut stream = self.docker.images().build( - &BuildOptions::builder(dockerfile_dir) - .tag(self.docker_image.clone()) - .build(), + let mut archive = tar::Builder::new(GzEncoder::new(Vec::default(), Compression::best())); + archive.append_dir_all(".", &dockerfile_dir).map_err(|e| { + error!("{:?}", e); + DockerError::BuildError + })?; + let bytes = archive.into_inner().and_then(|c| c.finish()).map_err(|e| { + error!("{:?}", e); + DockerError::BuildError + })?; + + let act = async move { + let mut stream = self.docker.build_image( + BuildImageOptions { + dockerfile: "Dockerfile".to_string(), + t: self.docker_image.clone(), + ..Default::default() + }, + None, + Some(bytes.into()), ); loop { if let Some(item) = stream.next().await { match item { Ok(output) => { - let msg = &output; - - if let Some(err_msg) = msg.get("error") { + if let Some(err_msg) = &output.error { error!("{:?}", err_msg.clone()); break Err(DockerError::BuildError); } else { - info!("{}", msg); + info!("{:?}", output); } } Err(e) => { @@ -277,7 +296,7 @@ impl DockerUtil { /// Inspect docker image and return its description as a json String pub fn inspect_image(&self) -> Result { let act = async { - match self.docker.images().get(&self.docker_image).inspect().await { + match self.docker.inspect_image(&self.docker_image).await { Ok(image) => Ok(json!(image)), Err(e) => { error!("{:?}", e); @@ -293,8 +312,11 @@ impl DockerUtil { fn extract_image(&self) -> Result<(Vec, Vec), DockerError> { // First try to find CMD parameters (together with potential ENV bindings) let act_cmd = async { - match self.docker.images().get(&self.docker_image).inspect().await { - Ok(image) => image.config.cmd.ok_or(DockerError::UnsupportedEntryPoint), + match self.docker.inspect_image(&self.docker_image).await { + Ok(image) => image + .config + .and_then(|c| c.cmd) + .ok_or(DockerError::UnsupportedEntryPoint), Err(e) => { error!("{:?}", e); Err(DockerError::InspectError) @@ -302,8 +324,11 @@ impl DockerUtil { } }; let act_env = async { - match self.docker.images().get(&self.docker_image).inspect().await { - Ok(image) => image.config.env.ok_or(DockerError::UnsupportedEntryPoint), + match self.docker.inspect_image(&self.docker_image).await { + Ok(image) => image + .config + .and_then(|c| c.env) + .ok_or(DockerError::UnsupportedEntryPoint), Err(e) => { error!("{:?}", e); Err(DockerError::InspectError) @@ -321,10 +346,10 @@ impl DockerUtil { // If no CMD instructions are found, try to locate an ENTRYPOINT command if check_cmd_runtime.is_err() || check_env_runtime.is_err() { let act_entrypoint = async { - match self.docker.images().get(&self.docker_image).inspect().await { + match self.docker.inspect_image(&self.docker_image).await { Ok(image) => image .config - .entrypoint + .and_then(|c| c.entrypoint) .ok_or(DockerError::UnsupportedEntryPoint), Err(e) => { error!("{:?}", e); @@ -342,10 +367,15 @@ impl DockerUtil { } let act = async { - match self.docker.images().get(&self.docker_image).inspect().await { + match self.docker.inspect_image(&self.docker_image).await { Ok(image) => Ok(( - image.config.entrypoint.unwrap(), - image.config.env.ok_or_else(Vec::::new).unwrap(), + image.config.clone().unwrap().entrypoint.unwrap(), + image + .config + .unwrap() + .env + .ok_or_else(Vec::::new) + .unwrap(), )), Err(e) => { error!("{:?}", e); @@ -360,8 +390,11 @@ impl DockerUtil { } let act = async { - match self.docker.images().get(&self.docker_image).inspect().await { - Ok(image) => Ok((image.config.cmd.unwrap(), image.config.env.unwrap())), + match self.docker.inspect_image(&self.docker_image).await { + Ok(image) => Ok(( + image.config.clone().unwrap().cmd.unwrap(), + image.config.unwrap().env.unwrap(), + )), Err(e) => { error!("{:?}", e); Err(DockerError::InspectError) @@ -389,8 +422,8 @@ impl DockerUtil { /// Fetch architecture information from an image pub fn architecture(&self) -> Result { let arch = async { - match self.docker.images().get(&self.docker_image).inspect().await { - Ok(image) => Ok(image.architecture), + match self.docker.inspect_image(&self.docker_image).await { + Ok(image) => Ok(image.architecture.unwrap_or_default()), Err(e) => { error!("{:?}", e); Err(DockerError::InspectError) diff --git a/enclave_build/src/lib.rs b/enclave_build/src/lib.rs index d8d41a199..12d9ccf52 100644 --- a/enclave_build/src/lib.rs +++ b/enclave_build/src/lib.rs @@ -74,7 +74,10 @@ impl<'a> Docker2Eif<'a> { metadata_path: Option, build_info: EifBuildInfo, ) -> Result { - let docker = DockerUtil::new(docker_image.clone()); + let docker = DockerUtil::new(docker_image.clone()).map_err(|e| { + eprintln!("Docker error: {e:?}"); + Docker2EifError::DockerError + })?; if !Path::new(&init_path).is_file() { return Err(Docker2EifError::InitPathError); diff --git a/src/common/mod.rs b/src/common/mod.rs index e50099acf..48ac09837 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -220,7 +220,7 @@ pub enum EnclaveProcessReply { } /// Struct that is passed along the backtrace and accumulates error messages. -#[derive(Debug, Default, PartialEq)] +#[derive(Debug, Default, PartialEq, Eq)] pub struct NitroCliFailure { /// Main action which was attempted and failed. pub action: String, diff --git a/tools/Dockerfile b/tools/Dockerfile index 344f2a8c9..fed947b3c 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -36,8 +36,8 @@ RUN source $HOME/.cargo/env && \ rustup toolchain install ${RUST_VERSION}-${ARCH}-unknown-linux-gnu && \ rustup default ${RUST_VERSION}-${ARCH}-unknown-linux-gnu && \ rustup target add --toolchain ${RUST_VERSION} ${ARCH}-unknown-linux-musl && \ - cargo install cargo-audit --version 0.16.0 --locked && \ - cargo install cargo-about --version 0.5.1 --locked + cargo install cargo-audit --version 0.17.6 --locked && \ + cargo install cargo-about --version 0.5.6 --locked # Install docker for nitro-cli build-enclave runs RUN apt-get update && \