diff --git a/cli/cmd/common.go b/cli/cmd/common.go index 33ebe50ea4..0b862eecf2 100644 --- a/cli/cmd/common.go +++ b/cli/cmd/common.go @@ -25,6 +25,7 @@ const ( settingsFilename = "settings.json" seedSharesFilename = "seed-shares.json" rulesFilename = "rules.rego" + layersCacheFilename = "layers-cache.json" verifyDir = "verify" cacheDirEnv = "CONTRAST_CACHE_DIR" ) diff --git a/cli/cmd/generate.go b/cli/cmd/generate.go index 13702f626f..d2313fc969 100644 --- a/cli/cmd/generate.go +++ b/cli/cmd/generate.go @@ -66,6 +66,7 @@ subcommands.`, cmd.Flags().StringP("policy", "p", rulesFilename, "path to policy (.rego) file") cmd.Flags().StringP("settings", "s", settingsFilename, "path to settings (.json) file") + cmd.Flags().StringP("genpolicy-cache-path", "c", layersCacheFilename, "path to cache for the cache (.json) file containing the image layers") cmd.Flags().StringP("manifest", "m", manifestFilename, "path to manifest (.json) file") cmd.Flags().String("reference-values", "", "set the default reference values used for attestation (one of: aks)") cmd.Flags().StringArrayP("workload-owner-key", "w", []string{workloadOwnerPEM}, @@ -104,7 +105,7 @@ func runGenerate(cmd *cobra.Command, args []string) error { } fmt.Fprintln(cmd.OutOrStdout(), "✔️ Patched targets") - if err := generatePolicies(cmd.Context(), flags.policyPath, flags.settingsPath, paths, log); err != nil { + if err := generatePolicies(cmd.Context(), flags.policyPath, flags.settingsPath, flags.genpolicyCachePath, paths, log); err != nil { return fmt.Errorf("failed to generate policies: %w", err) } fmt.Fprintln(cmd.OutOrStdout(), "✔️ Generated workload policy annotations") @@ -235,7 +236,7 @@ func filterNonCoCoRuntime(runtimeClassNamePrefix string, paths []string, logger return filtered } -func generatePolicies(ctx context.Context, regoRulesPath, policySettingsPath string, yamlPaths []string, logger *slog.Logger) error { +func generatePolicies(ctx context.Context, regoRulesPath, policySettingsPath, genpolicyCachePath string, yamlPaths []string, logger *slog.Logger) error { if err := createFileWithDefault(policySettingsPath, func() ([]byte, error) { return defaultGenpolicySettings, nil }); err != nil { return fmt.Errorf("creating default policy file: %w", err) } @@ -256,7 +257,7 @@ func generatePolicies(ctx context.Context, regoRulesPath, policySettingsPath str } }() for _, yamlPath := range yamlPaths { - policyHash, err := generatePolicyForFile(ctx, genpolicyInstall.Path(), regoRulesPath, policySettingsPath, yamlPath, logger) + policyHash, err := generatePolicyForFile(ctx, genpolicyInstall.Path(), regoRulesPath, policySettingsPath, yamlPath, genpolicyCachePath, logger) if err != nil { return fmt.Errorf("failed to generate policy for %s: %w", yamlPath, err) } @@ -488,14 +489,14 @@ func (l logTranslator) stop() { <-l.stopDoneC } -func generatePolicyForFile(ctx context.Context, genpolicyPath, regoPath, policyPath, yamlPath string, logger *slog.Logger) ([32]byte, error) { +func generatePolicyForFile(ctx context.Context, genpolicyPath, regoPath, policyPath, yamlPath, genpolicyCachePath string, logger *slog.Logger) ([32]byte, error) { args := []string{ "--raw-out", - "--use-cached-files", fmt.Sprintf("--runtime-class-names=%s", "contrast-cc"), fmt.Sprintf("--rego-rules-path=%s", regoPath), fmt.Sprintf("--json-settings-path=%s", policyPath), fmt.Sprintf("--yaml-file=%s", yamlPath), + fmt.Sprintf("--layers-cache-file-path=%s", genpolicyCachePath), } genpolicy := exec.CommandContext(ctx, genpolicyPath, args...) genpolicy.Env = append(genpolicy.Env, "RUST_LOG=info", "RUST_BACKTRACE=1") @@ -577,6 +578,7 @@ type generateFlags struct { policyPath string settingsPath string manifestPath string + genpolicyCachePath string referenceValues string workloadOwnerKeys []string seedshareOwnerKeys []string @@ -595,6 +597,10 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) { if err != nil { return nil, err } + genpolicyCachePath, err := cmd.Flags().GetString("genpolicy-cache-path") + if err != nil { + return nil, err + } manifestPath, err := cmd.Flags().GetString("manifest") if err != nil { return nil, err @@ -627,6 +633,9 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) { if !cmd.Flags().Changed("settings") { settingsPath = filepath.Join(workspaceDir, settingsFilename) } + if !cmd.Flags().Changed("genpolicy-cache-path") { + genpolicyCachePath = filepath.Join(workspaceDir, genpolicyCachePath) + } if !cmd.Flags().Changed("policy") { policyPath = filepath.Join(workspaceDir, rulesFilename) } @@ -654,6 +663,7 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) { return &generateFlags{ policyPath: policyPath, settingsPath: settingsPath, + genpolicyCachePath: genpolicyCachePath, manifestPath: manifestPath, referenceValues: referenceValues, workloadOwnerKeys: workloadOwnerKeys, diff --git a/packages/by-name/microsoft/genpolicy/genpolicy_msft_cache_path.patch b/packages/by-name/microsoft/genpolicy/genpolicy_msft_cache_path.patch new file mode 100644 index 0000000000..20a8e9559f --- /dev/null +++ b/packages/by-name/microsoft/genpolicy/genpolicy_msft_cache_path.patch @@ -0,0 +1,260 @@ +From 6a3ed38140ab8e1026f1aca4e3b41646af5b33d9 Mon Sep 17 00:00:00 2001 +From: Leonard Cohnen +Date: Mon, 17 Jun 2024 22:39:23 +0200 +Subject: [PATCH] genpolicy: allow specifying layer cache file + +Add --layers-cache-file-path flag to allow the user to +specify where the cache file for the container layers +is saved. This allows e.g. to have one cache file +independent of the user's working directory. + +NOTE: This patch file has been heavily modified so that it +applies to the microsoft/kata-containers fork. +Still, of the original commit is adopted at some point, +this patch can be dropped. + +Signed-off-by: Leonard Cohnen +--- + src/tools/genpolicy/src/registry.rs | 28 +++++++++-------- + .../genpolicy/src/registry_containerd.rs | 30 +++++++++++-------- + src/tools/genpolicy/src/utils.rs | 16 ++++++++++ + .../kubernetes/k8s-policy-pod.bats | 14 +++++++++ + 4 files changed, 63 insertions(+), 25 deletions(-) + +diff --git a/src/tools/genpolicy/src/registry.rs b/src/tools/genpolicy/src/registry.rs +index 87d81fb2c..e30405808 100644 +--- a/src/tools/genpolicy/src/registry.rs ++++ b/src/tools/genpolicy/src/registry.rs +@@ -67,7 +67,7 @@ pub struct ImageLayer { + } + + impl Container { +- pub async fn new(use_cached_files: bool, image: &str) -> Result { ++ pub async fn new(layers_cache_file_path: Option, image: &str) -> Result { + info!("============================================"); + info!("Pulling manifest and config for {:?}", image); + let reference: Reference = image.to_string().parse().unwrap(); +@@ -96,7 +96,7 @@ impl Container { + let config_layer: DockerConfigLayer = + serde_json::from_str(&config_layer_str).unwrap(); + let image_layers = get_image_layers( +- use_cached_files, ++ layers_cache_file_path, + &mut client, + &reference, + &manifest, +@@ -227,7 +227,7 @@ impl Container { + } + + async fn get_image_layers( +- use_cached_files: bool, ++ layers_cache_file_path: Option, + client: &mut Client, + reference: &Reference, + manifest: &manifest::OciImageManifest, +@@ -246,7 +246,7 @@ async fn get_image_layers( + layers.push(ImageLayer { + diff_id: config_layer.rootfs.diff_ids[layer_index].clone(), + verity_hash: get_verity_hash( +- use_cached_files, ++ layers_cache_file_path.clone(), + client, + reference, + &layer.digest, +@@ -266,7 +266,7 @@ async fn get_image_layers( + } + + async fn get_verity_hash( +- use_cached_files: bool, ++ layers_cache_file_path: Option, + client: &mut Client, + reference: &Reference, + layer_digest: &str, +@@ -274,7 +274,6 @@ async fn get_verity_hash( + ) -> Result { + let temp_dir = tempfile::tempdir_in(".")?; + let base_dir = temp_dir.path(); +- let cache_file = "layers-cache.json"; + // Use file names supported by both Linux and Windows. + let file_name = str::replace(layer_digest, ":", "-"); + let mut decompressed_path = base_dir.join(file_name); +@@ -288,8 +287,8 @@ async fn get_verity_hash( + let mut error = false; + + // get value from store and return if it exists +- if use_cached_files { +- verity_hash = read_verity_from_store(cache_file, diff_id)?; ++ if let Some(path) = layers_cache_file_path.as_ref() { ++ verity_hash = read_verity_from_store(path, diff_id)?; + info!("Using cache file"); + info!("dm-verity root hash: {verity_hash}"); + } +@@ -317,8 +316,8 @@ async fn get_verity_hash( + } + Ok(v) => { + verity_hash = v; +- if use_cached_files { +- add_verity_to_store(cache_file, diff_id, &verity_hash)?; ++ if let Some(path) = layers_cache_file_path.as_ref() { ++ add_verity_to_store(path, diff_id, &verity_hash)?; + } + info!("dm-verity root hash: {verity_hash}"); + } +@@ -329,8 +328,8 @@ async fn get_verity_hash( + temp_dir.close()?; + if error { + // remove the cache file if we're using it +- if use_cached_files { +- std::fs::remove_file(cache_file)?; ++ if let Some(path) = layers_cache_file_path.as_ref() { ++ std::fs::remove_file(path)?; + } + warn!("{error_message}"); + } +@@ -457,9 +456,14 @@ pub fn get_verity_hash_value(path: &Path) -> Result { + + pub async fn get_container(config: &Config, image: &str) -> Result { + if let Some(socket_path) = &config.containerd_socket_path { +- return Container::new_containerd_pull(config.use_cache, image, socket_path).await; ++ return Container::new_containerd_pull( ++ config.layers_cache_file_path.clone(), ++ image, ++ socket_path, ++ ) ++ .await; + } +- Container::new(config.use_cache, image).await ++ Container::new(config.layers_cache_file_path.clone(), image).await + } + + fn build_auth(reference: &Reference) -> RegistryAuth { +diff --git a/src/tools/genpolicy/src/registry_containerd.rs b/src/tools/genpolicy/src/registry_containerd.rs +index c63a73f3d..c1338e1ba 100644 +--- a/src/tools/genpolicy/src/registry_containerd.rs ++++ b/src/tools/genpolicy/src/registry_containerd.rs +@@ -28,7 +28,7 @@ use tower::service_fn; + + impl Container { + pub async fn new_containerd_pull( +- use_cached_files: bool, ++ layers_cache_file_path: Option, + image: &str, + containerd_socket_path: &str, + ) -> Result { +@@ -58,8 +58,13 @@ impl Container { + let config_layer = get_config_layer(image_ref_str, k8_cri_image_client) + .await + .unwrap(); +- let image_layers = +- get_image_layers(use_cached_files, &manifest, &config_layer, &ctrd_client).await?; ++ let image_layers = get_image_layers( ++ layers_cache_file_path, ++ &manifest, ++ &config_layer, ++ &ctrd_client, ++ ) ++ .await?; + + Ok(Container { + config_layer, +@@ -242,7 +247,7 @@ pub fn build_auth(reference: &Reference) -> Option { + } + + pub async fn get_image_layers( +- use_cached_files: bool, ++ layers_cache_file_path: Option, + manifest: &serde_json::Value, + config_layer: &DockerConfigLayer, + client: &containerd_client::Client, +@@ -261,7 +266,7 @@ pub async fn get_image_layers( + let imageLayer = ImageLayer { + diff_id: config_layer.rootfs.diff_ids[layer_index].clone(), + verity_hash: get_verity_hash( +- use_cached_files, ++ layers_cache_file_path.clone(), + layer["digest"].as_str().unwrap(), + client, + &config_layer.rootfs.diff_ids[layer_index].clone(), +@@ -280,7 +285,7 @@ pub async fn get_image_layers( + } + + async fn get_verity_hash( +- use_cached_files: bool, ++ layers_cache_file_path: Option, + layer_digest: &str, + client: &containerd_client::Client, + diff_id: &str, +@@ -300,8 +305,8 @@ async fn get_verity_hash( + let mut error_message = "".to_string(); + let mut error = false; + +- if use_cached_files { +- verity_hash = read_verity_from_store(cache_file, diff_id)?; ++ if let Some(path) = layers_cache_file_path.as_ref() { ++ verity_hash = read_verity_from_store(path, diff_id)?; + info!("Using cache file"); + info!("dm-verity root hash: {verity_hash}"); + } +@@ -328,8 +333,8 @@ async fn get_verity_hash( + } + Ok(v) => { + verity_hash = v; +- if use_cached_files { +- add_verity_to_store(cache_file, diff_id, &verity_hash)?; ++ if let Some(path) = layers_cache_file_path.as_ref() { ++ add_verity_to_store(path, diff_id, &verity_hash)?; + } + info!("dm-verity root hash: {verity_hash}"); + } +@@ -339,8 +344,8 @@ async fn get_verity_hash( + temp_dir.close()?; + if error { + // remove the cache file if we're using it +- if use_cached_files { +- std::fs::remove_file(cache_file)?; ++ if let Some(path) = layers_cache_file_path.as_ref() { ++ std::fs::remove_file(path)?; + } + warn!("{error_message}"); + } +diff --git a/src/tools/genpolicy/src/utils.rs b/src/tools/genpolicy/src/utils.rs +index e0d4b84be..24933075a 100644 +--- a/src/tools/genpolicy/src/utils.rs ++++ b/src/tools/genpolicy/src/utils.rs +@@ -88,3 +89,11 @@ struct CommandLineOptions { + containerd_socket_path: Option, + ++ #[clap( ++ long, ++ help = "Path to the layers cache file. This file is used to store the layers cache information. The default value is ./layers-cache.json.", ++ default_missing_value = "./layers-cache.json", ++ require_equals = true ++ )] ++ layers_cache_file_path: Option, ++ + #[clap(short, long, help = "Print version information and exit")] +@@ -106,4 +115,5 @@ pub struct Config { + pub raw_out: bool, + pub base64_out: bool, + pub containerd_socket_path: Option, ++ pub layers_cache_file_path: Option, + pub version: bool, +@@ -123,2 +133,9 @@ impl Config { + let args = CommandLineOptions::parse(); ++ ++ let mut layers_cache_file_path = args.layers_cache_file_path; ++ // preserve backwards compatibility for only using the `use_cached_files` flag ++ if args.use_cached_files && layers_cache_file_path.is_none() { ++ layers_cache_file_path = Some(String::from("./layers-cache.json")); ++ } ++ + Self { +@@ -106,7 +153,8 @@ impl Config { + raw_out: args.raw_out, + base64_out: args.base64_out, + containerd_socket_path: args.containerd_socket_path, ++ layers_cache_file_path: layers_cache_file_path, + version: args.version, + } + } + } diff --git a/packages/by-name/microsoft/genpolicy/package.nix b/packages/by-name/microsoft/genpolicy/package.nix index b78a3c3d9c..63908208d1 100644 --- a/packages/by-name/microsoft/genpolicy/package.nix +++ b/packages/by-name/microsoft/genpolicy/package.nix @@ -37,6 +37,7 @@ rustPlatform.buildRustPackage rec { sha256 = "sha256-wBOyrFY4ZdWBjF5bIrHm7CFy6lVclcvwhF85wXpFZoc="; }) ./genpolicy_msft_runtime_class_filter.patch + ./genpolicy_msft_cache_path.patch ]; };