This repository has been archived by the owner on Jan 15, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API and CLI to update detached metadata
In the FCOS use case and I'm sure others, we want a flow where we do a build (including a container image) and then once it's ready, we sign it by passing the commit metadata to a separate system. Basically what we want is the ability to update the detached metadata object in an exported container image. Now, I'm regretting the design choice to have the container flow reuse the tar path of having the signature be part of the tar stream instead of part of the container metadata, because it *greatly* complicates things here, particularly in terms of handling chunked images. We want to preserve all metadata and other layers in the image; we just need to add/replace a single entry in the layer that has the ostree metadata. Except, because this ostree layer gets its own special label in the container image metadata, we need to update that label. What would make this a lot easier is if we had write support via skopeo/containers-image-proxy. Because we don't, given an image on a remote registry, right now we pull the whole thing down into a temporary OCI directory, even though we only want to mutate one layer. Closes: #295
- Loading branch information
Showing
7 changed files
with
324 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
use super::ImageReference; | ||
use crate::container::{ocidir, skopeo}; | ||
use crate::container::{store as container_store, Transport}; | ||
use anyhow::{anyhow, Context, Result}; | ||
use camino::Utf8Path; | ||
use std::io::{BufReader, BufWriter}; | ||
use std::rc::Rc; | ||
|
||
/// Given an OSTree container image reference, update the detached metadata (e.g. GPG signature) | ||
/// while preserving all other container image metadata. | ||
/// | ||
/// The return value is the manifest digest of (e.g. `@sha256:`) the image. | ||
pub async fn update_detached_metadata( | ||
src: &ImageReference, | ||
dest: &ImageReference, | ||
detached_buf: Option<&[u8]>, | ||
) -> Result<String> { | ||
// For now, convert the source to a temporary OCI directory, so we can directly | ||
// parse and manipulate it. In the future this will be replaced by https://github.com/ostreedev/ostree-rs-ext/issues/153 | ||
// and other work to directly use the containers/image API via containers-image-proxy. | ||
let tempdir = tempfile::tempdir_in("/var/tmp")?; | ||
let tempsrc = tempdir.path().join("src"); | ||
let tempsrc_utf8 = Utf8Path::from_path(&tempsrc).ok_or_else(|| anyhow!("Invalid tempdir"))?; | ||
let tempsrc_ref = ImageReference { | ||
transport: Transport::OciDir, | ||
name: tempsrc_utf8.to_string(), | ||
}; | ||
|
||
// Full copy of the source image | ||
let pulled_digest: String = skopeo::copy(src, &tempsrc_ref) | ||
.await | ||
.context("Creating temporary copy to OCI dir")?; | ||
|
||
// Copy to the thread | ||
let detached_buf = detached_buf.map(Vec::from); | ||
let tempsrc_ref_path = tempsrc_ref.name.clone(); | ||
// Fork a thread to do the heavy lifting of filtering the tar stream, rewriting the manifest/config. | ||
crate::tokio_util::spawn_blocking_cancellable_flatten(move |cancellable| { | ||
// Open the temporary OCI directory. | ||
let tempsrc = Rc::new(openat::Dir::open(tempsrc_ref_path).context("Opening src")?); | ||
let tempsrc = ocidir::OciDir::open(tempsrc)?; | ||
|
||
// Load the manifest, platform, and config | ||
let (mut manifest, manifest_descriptor) = tempsrc | ||
.read_manifest_and_descriptor() | ||
.context("Reading manifest from source")?; | ||
anyhow::ensure!(manifest_descriptor.digest().as_str() == pulled_digest.as_str()); | ||
let platform = manifest_descriptor | ||
.platform() | ||
.as_ref() | ||
.cloned() | ||
.unwrap_or_default(); | ||
let mut config: oci_spec::image::ImageConfiguration = | ||
tempsrc.read_json_blob(manifest.config())?; | ||
let mut ctrcfg = config | ||
.config() | ||
.as_ref() | ||
.cloned() | ||
.ok_or_else(|| anyhow!("Image is missing container configuration"))?; | ||
|
||
// Find the OSTree commit layer we want to replace | ||
let commit_layer = container_store::ostree_layer(&manifest, &config)?; | ||
let commit_layer_idx = manifest | ||
.layers() | ||
.iter() | ||
.position(|x| x == commit_layer) | ||
.unwrap(); | ||
|
||
// Create a new layer | ||
let out_layer = { | ||
// Create tar streams for source and destination | ||
let src_layer = BufReader::new(tempsrc.read_blob(commit_layer)?); | ||
let mut src_layer = flate2::read::GzDecoder::new(src_layer); | ||
let mut out_layer = BufWriter::new(tempsrc.create_raw_layer(None)?); | ||
|
||
// Process the tar stream and inject our new detached metadata | ||
crate::tar::update_detached_metadata( | ||
&mut src_layer, | ||
&mut out_layer, | ||
detached_buf.as_deref(), | ||
Some(cancellable), | ||
)?; | ||
|
||
// Flush all wrappers, and finalize the layer | ||
out_layer | ||
.into_inner() | ||
.map_err(|_| anyhow!("Failed to flush buffer"))? | ||
.complete()? | ||
}; | ||
// Get the diffid and descriptor for our new tar layer | ||
let out_layer_diffid = format!("sha256:{}", out_layer.uncompressed_sha256); | ||
let out_layer_descriptor = out_layer | ||
.descriptor() | ||
.media_type(oci_spec::image::MediaType::ImageLayerGzip) | ||
.build() | ||
.unwrap(); // SAFETY: We pass all required fields | ||
|
||
// Splice it into both the manifest and config | ||
manifest.layers_mut()[commit_layer_idx] = out_layer_descriptor; | ||
config.rootfs_mut().diff_ids_mut()[commit_layer_idx] = out_layer_diffid.clone(); | ||
|
||
let labels = ctrcfg.labels_mut().get_or_insert_with(Default::default); | ||
labels.insert( | ||
crate::container::OSTREE_DIFFID_LABEL.into(), | ||
out_layer_diffid, | ||
); | ||
config.set_config(Some(ctrcfg)); | ||
|
||
// Write the config and manifest | ||
let new_config_descriptor = tempsrc.write_config(config)?; | ||
manifest.set_config(new_config_descriptor); | ||
// This entirely replaces the single entry in the OCI directory, which skopeo will find by default. | ||
tempsrc | ||
.write_manifest(manifest, platform) | ||
.context("Writing manifest")?; | ||
Ok(()) | ||
}) | ||
.await | ||
.context("Regenerating commit layer")?; | ||
|
||
// Finally, copy the mutated image back to the target. For chunked images, | ||
// because we only changed one layer, skopeo should know not to re-upload shared blobs. | ||
crate::container::skopeo::copy(&tempsrc_ref, dest) | ||
.await | ||
.context("Copying to destination") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.