Skip to content

Commit

Permalink
Update build artifact extraction for new enum types
Browse files Browse the repository at this point in the history
Signed-off-by: Robert Detjens <[email protected]>
  • Loading branch information
detjensrobert committed Feb 1, 2025
1 parent 4f0545d commit e3b8edd
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 67 deletions.
1 change: 1 addition & 0 deletions src/access_handlers/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub async fn check(profile_name: &str) -> Result<()> {
let registry_config = &get_config()?.registry;

let client = docker().await?;

// build test image string
// registry.example.com/somerepo/testimage:pleaseignore
let test_image = format!("{}/credstestimage", registry_config.domain);
Expand Down
95 changes: 83 additions & 12 deletions src/builder/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::configparser::challenge::{ChallengeConfig, ProvideConfig};
pub async fn extract_asset(
chal: &ChallengeConfig,
provide: &ProvideConfig,
// pod_containers:
profile_name: &str,
) -> Result<Vec<PathBuf>> {
// This needs to handle three cases * 2 sources:
// - single or multiple files without renaming (no as: field)
Expand All @@ -31,36 +31,104 @@ pub async fn extract_asset(
// TODO: since this puts artifacts in the repo source folder, this should
// try to not overwrite any existing files.

// debug!("extracting assets from container {}", &container.name);
debug!(
"extracting assets for challenge {:?} provide {:?}",
chal.directory, &provide
);

let docker = docker().await?;

match provide {
// Repo file paths are relative to the challenge directory, so prepend chal dir

// No action necessary, return path as-is
ProvideConfig::FromRepo { files } => Ok(files.clone()),
ProvideConfig::FromRepo { files } => {
Ok(files.iter().map(|f| chal.directory.join(f)).collect_vec())
}
ProvideConfig::FromRepoRename { from, to } => {
std::fs::rename(from, to)?;
std::fs::copy(chal.directory.join(from), chal.directory.join(to))
.with_context(|| format!("could not copy repo file {from:?} to {to:?}"))?;
Ok(vec![to.clone()])
}
ProvideConfig::FromRepoArchive {
files,
archive_name,
} => {
zip_files(archive_name, files)?;
zip_files(
&chal.directory.join(archive_name),
&files.iter().map(|f| chal.directory.join(f)).collect_vec(),
)
.with_context(|| format!("could not create archive {archive_name:?}"))?;
Ok(vec![archive_name.clone()])
}

ProvideConfig::FromContainer { container, files } => extract_files(chal, container, files),
ProvideConfig::FromContainer {
container: container_name,
files,
} => {
let tag = chal.container_tag_for_pod(profile_name, container_name)?;

let name = format!(
"asset-container-{}-{}",
chal.directory.to_string_lossy().replace("/", "-"),
container_name
);
let container = docker::create_container(&tag, &name).await?;

let files = extract_files(chal, &container, files).await;

docker::remove_container(container).await?;

files
}
.with_context(|| format!("could not copy files {files:?} from container {container_name}")),

ProvideConfig::FromContainerRename {
container,
container: container_name,
from,
to,
} => extract_rename(chal, container, from, to),
} => {
let tag = chal.container_tag_for_pod(profile_name, container_name)?;

let name = format!(
"asset-container-{}-{}",
chal.directory.to_string_lossy().replace("/", "-"),
container_name
);
let container = docker::create_container(&tag, &name).await?;

let files = extract_rename(chal, &container, from, &chal.directory.join(to)).await;

docker::remove_container(container).await?;

files
}
.with_context(|| format!("could not copy file {from:?} from container {container_name}")),

ProvideConfig::FromContainerArchive {
container,
container: container_name,
files,
archive_name,
} => extract_archive(chal, container, files, archive_name),
} => {
let tag = chal.container_tag_for_pod(profile_name, container_name)?;

let name = format!(
"asset-container-{}-{}",
chal.directory.to_string_lossy().replace("/", "-"),
container_name
);
let container = docker::create_container(&tag, &name).await?;

let files =
extract_archive(chal, &container, files, &chal.directory.join(archive_name)).await;

docker::remove_container(container).await?;

files
}
.with_context(|| {
format!("could not create archive {archive_name:?} from container {container_name}")
}),
}
}

Expand Down Expand Up @@ -114,14 +182,17 @@ async fn extract_archive(
);

// copy all listed files to tempdir
let tempdir = tempdir_in(".")?;
let tempdir = tempfile::Builder::new()
.prefix(".beavercds-archive-")
.tempdir_in(".")?;
let copied_files = try_join_all(files.iter().map(|from| async {
let to = tempdir.path().join(from.file_name().unwrap());
docker::copy_file(container, from, &to).await
}))
.await?;

zip_files(&chal.directory.join(archive_name), &copied_files)?;
// archive_name already has the chal dir prepended
zip_files(archive_name, &copied_files)?;

Ok(vec![chal.directory.join(archive_name)])
}
Expand Down
7 changes: 4 additions & 3 deletions src/builder/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ pub async fn push_image(image_tag: &str, creds: &UserPass) -> Result<String> {
Ok(tag.to_string())
}

#[tokio::main(flavor = "current_thread")] // make this a sync function
pub async fn create_container(image_tag: &str, name: &str) -> Result<ContainerInfo> {
debug!("creating container {name:?} from image {image_tag:?}");
let client = docker().await?;
Expand All @@ -143,7 +142,6 @@ pub async fn create_container(image_tag: &str, name: &str) -> Result<ContainerIn
})
}

#[tokio::main(flavor = "current_thread")] // make this a sync function
pub async fn remove_container(container: ContainerInfo) -> Result<()> {
debug!("removing container {}", &container.name);
let client = docker().await?;
Expand Down Expand Up @@ -182,7 +180,10 @@ pub async fn copy_file(container: &ContainerInfo, from: &Path, to: &Path) -> Res
});

// collect byte stream chunks into full file
let mut temptar = Builder::new().suffix(".tar").tempfile_in(".")?;
let mut temptar = Builder::new()
.prefix(".beavercds-")
.suffix(".tar")
.tempfile_in(".")?;
while let Some(chunk) = dl_stream.next().await {
temptar.as_file_mut().write_all(&chunk?)?;
}
Expand Down
67 changes: 17 additions & 50 deletions src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

use anyhow::{anyhow, Context, Error, Result};
use bollard::image::BuildImageOptions;
use futures::stream::Iter;
use futures::future::try_join_all;
use futures::stream::{FuturesOrdered, Iter};
use itertools::Itertools;
use simplelog::*;
use std::default;
Expand Down Expand Up @@ -123,55 +124,21 @@ fn build_challenge(
if extract_artifacts {
info!("extracting build artifacts for chal {:?}", chal.directory);

// let (provide_container, provide_static): (Vec<_>, Vec<_>) =
// chal.provide.iter().partition(|p| p.from.is_some());

// let extracted_files = provide_container
// .iter()
// // associate container `Provide` entries with their corresponding container image
// .map(|provide| {
// (
// provide,
// chal.container_tag_for_pod(profile_name, provide.from.as_ref().unwrap()),
// )
// })
// // extract each container provide entry
// .map(|(p, tag)| {
// let tag = tag?;

// let name = format!(
// "asset-container-{}-{}",
// chal.directory.to_string_lossy().replace("/", "-"),
// p.from.clone().unwrap()
// );
// let container = docker::create_container(&tag, &name)?;

// let asset_result =
// artifacts::extract_asset(chal, p, &container).with_context(|| {
// format!(
// "failed to extract build artifacts for chal {:?} container {:?}",
// chal.directory,
// p.from.clone().unwrap()
// )
// });

// // clean up container even if it failed
// docker::remove_container(container)?;

// asset_result
// })
// .flatten_ok()
// .collect::<Result<Vec<_>>>()?;

// // handle potentially zipping up local files as well
// let local_files = provide_static.iter().map(|provide| {
// match provide.as_file.as_ref() {
// // no archiving needed, pass files as-is
// None => Ok(provide.include.clone()),
// // need to archive multiple files into zip
// Some(as_) => artifacts::zip_files(as_, provide.include.as_ref()).map(|z| vec![z]),
// }
// });
// extract each challenge provide entry
// this handles both local files and from build containers
let extracted_files = chal
.provide
.iter()
.map(|p| {
artifacts::extract_asset(chal, p, profile_name).with_context(|| {
format!(
"failed to extract build artifacts for chal {:?}",
chal.directory,
)
})
})
.flatten_ok()
.collect::<Result<Vec<_>>>()?;

info!("extracted artifacts: {:?}", built.assets);
}
Expand Down
1 change: 1 addition & 0 deletions src/configparser/challenge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ enum FlagType {
}

// Parse each distinct kind of Provide action as a separate enum variant
// TODO: enforce relative/absolute paths for repo/container Provide's (`validator` crate?)
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
#[fully_pub]
Expand Down
4 changes: 2 additions & 2 deletions tests/repo/rcds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ points:
deploy:
# control challenge deployment status explicitly per environment/profile
testing:
misc/garf: true
# misc/garf: true
pwn/notsh: true
web/bar: true
# web/bar: true

profiles:
# configure per-environment credentials etc
Expand Down

0 comments on commit e3b8edd

Please sign in to comment.