diff --git a/Cargo.toml b/Cargo.toml index 5fecbf9d3..6e76dc4a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,20 +12,22 @@ edition = "2018" anyhow = "1" async-compression = { version = "0.3.15", features = ["futures-io", "tokio", "gzip", "zstd"] } async-trait = "0.1.56" -attestation_agent = { git = "https://github.com/confidential-containers/attestation-agent.git", rev = "55db121", optional = true } +attestation_agent = { git = "https://github.com/confidential-containers/attestation-agent.git", rev = "280c805", optional = true } base64 = "0.13.0" +cfg-if = { version = "1.0.0", optional = true } dircpy = { version = "0.3.12", optional = true } flate2 = "1.0" flume = "0.10.14" fs_extra = { version = "1.2.0", optional = true } futures-util = "0.3" hex = { version = "0.4.3", optional = true } +lazy_static = { version = "1.4.0", optional = true } libc = "0.2" log = "0.4.14" nix = { version = "0.26", optional = true } oci-distribution = "0.9.4" oci-spec = "0.5.8" -ocicrypt-rs = { git = "https://github.com/confidential-containers/ocicrypt-rs.git", rev = "16e07c7", default-features = false, features = ["keywrap-jwe", "async-io"], optional = true } +ocicrypt-rs = { git = "https://github.com/confidential-containers/ocicrypt-rs.git", rev = "b720152", default-features = false, features = ["keywrap-jwe", "async-io"], optional = true } prost = { version = "0.11", optional = true } sequoia-openpgp = { version = "1.7.0", default-features = false, features = ["compression", "crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"], optional = true } protobuf = { version = "3.2.0", optional = true } @@ -41,7 +43,7 @@ strum_macros = "0.24" tar = "0.4.37" tokio = "1.0" tonic = { version = "0.8", optional = true } -url = { version = "2.2.2", optional = true } +url = "2.2.2" ttrpc = { version = "0.7.1", features = [ "async" ], optional = true } walkdir = "2" zstd = "0.11" @@ -83,10 +85,10 @@ keywrap-ttrpc = ["ocicrypt-rs/keywrap-keyprovider-ttrpc", "dep:ttrpc", "dep:prot eaa-kbc = ["attestation_agent/eaa_kbc", "ocicrypt-rs/eaa_kbc"] signature = ["hex"] -signature-simple = ["signature", "sequoia-openpgp", "serde_yaml", "url"] +signature-simple = ["signature", "sequoia-openpgp", "serde_yaml"] signature-cosign = ["signature", "sigstore"] snapshot-overlayfs = ["nix"] snapshot-unionfs = ["nix", "dircpy", "fs_extra"] -getresource = [] +getresource = [ "lazy_static", "cfg-if" ] diff --git a/build.rs b/build.rs index f8cf762f7..2d4bfcac1 100644 --- a/build.rs +++ b/build.rs @@ -11,7 +11,7 @@ fn main() -> Result<()> { #[cfg(feature = "ttrpc-codegen")] ttrpc_codegen::Codegen::new() - .out_dir("./src/secure_channel/ttrpc_proto") + .out_dir("./src/resource/kbs/ttrpc_proto") .input("./protos/getresource.proto") .include("./protos") .rust_protobuf() diff --git a/protos/getresource.proto b/protos/getresource.proto index fb9537570..6336da010 100644 --- a/protos/getresource.proto +++ b/protos/getresource.proto @@ -3,9 +3,8 @@ syntax = "proto3"; package getresource; message GetResourceRequest { - string KbcName = 1; - string KbsUri = 2; - string ResourceDescription = 3; + string ResourceUri = 1; + string KbcName = 2; } message GetResourceResponse { diff --git a/scripts/build_attestation_agent.sh b/scripts/build_attestation_agent.sh index 6e5bcaecb..f4d829965 100755 --- a/scripts/build_attestation_agent.sh +++ b/scripts/build_attestation_agent.sh @@ -20,7 +20,7 @@ SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) AA_DIR=$SCRIPT_DIR/attestation_agent pushd $SCRIPT_DIR -git clone --depth 1 "https://github.com/confidential-containers/attestation-agent.git" $AA_DIR +git clone "https://github.com/confidential-containers/attestation-agent.git" $AA_DIR pushd $AA_DIR make $parameters diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 4965a40d6..42bcd5a33 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -5,7 +5,7 @@ pub mod auth_config; -use std::{collections::HashMap, fs::File, io::BufReader, path::Path}; +use std::collections::HashMap; use anyhow::*; use oci_distribution::{secrets::RegistryAuth, Reference}; @@ -26,53 +26,15 @@ pub struct DockerAuthConfig { } /// Get a credential (RegistryAuth) for the given Reference. -/// First, it will try to find auth info in the local -/// `auth.json`. If there is not one, it will -/// ask one from the [`crate::secure_channel::SecureChannel`], which connects -/// to the GetResource API of Attestation Agent. -/// Then, it will use the `auth.json` to find -/// a credential of the given image reference. -#[cfg(feature = "getresource")] +/// The path can be from different places. Like `path://` or +/// `kbs://`. pub async fn credential_for_reference( reference: &Reference, - secure_channel: std::sync::Arc>, auth_file_path: &str, ) -> Result { - // if Policy config file does not exist, get if from KBS. - if !Path::new(auth_file_path).exists() { - secure_channel - .lock() - .await - .get_resource(RESOURCE_DESCRIPTION, HashMap::new(), auth_file_path) - .await?; - } + let auth = crate::resource::get_resource(auth_file_path).await?; - let reader = File::open(auth_file_path)?; - let buf_reader = BufReader::new(reader); - let config: DockerConfigFile = serde_json::from_reader(buf_reader)?; - - // TODO: support credential helpers - auth_config::credential_from_auth_config(reference, &config.auths) -} - -/// Get a credential (RegistryAuth) for the given Reference. -/// First, it will try to find auth info in the local -/// `auth.json`. If there is not one, it will -/// directly return [`RegistryAuth::Anonymous`]. -/// Or, it will use the `auth.json` to find -/// a credential of the given image reference. -pub async fn credential_for_reference_local( - reference: &Reference, - auth_file_path: &str, -) -> Result { - // if Policy config file does not exist, get if from KBS. - if !Path::new(auth_file_path).exists() { - return Ok(RegistryAuth::Anonymous); - } - - let reader = File::open(auth_file_path)?; - let buf_reader = BufReader::new(reader); - let config: DockerConfigFile = serde_json::from_reader(buf_reader)?; + let config: DockerConfigFile = serde_json::from_slice(&auth)?; // TODO: support credential helpers auth_config::credential_from_auth_config(reference, &config.auths) diff --git a/src/config.rs b/src/config.rs index 5852ffb25..33c26501a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,26 +14,22 @@ use crate::CC_IMAGE_WORK_DIR; const DEFAULT_WORK_DIR: &str = "/var/lib/image-rs/"; /// Default policy file path. -pub const POLICY_FILE_PATH: &str = "/run/image-security/security_policy.json"; +pub const POLICY_FILE_PATH: &str = "kbs:///default/security-policy/test"; /// Dir of Sigstore Config file. /// The reason for using the `/run` directory here is that in general HW-TEE, /// the `/run` directory is mounted in `tmpfs`, which is located in the encrypted memory protected by HW-TEE. pub const SIG_STORE_CONFIG_DIR: &str = "/run/image-security/simple_signing/sigstore_config"; -pub const SIG_STORE_CONFIG_DEFAULT_FILE: &str = - "/run/image-security/simple_signing/sigstore_config/default.yaml"; +pub const SIG_STORE_CONFIG_DEFAULT_FILE: &str = "kbs:///default/sigstore-config/test"; /// Path to the gpg pubkey ring of the signature pub const GPG_KEY_RING: &str = "/run/image-security/simple_signing/pubkey.gpg"; -/// Dir for storage of cosign verification keys. -pub const COSIGN_KEY_DIR: &str = "/run/image-security/cosign"; - /// The reason for using the `/run` directory here is that in general HW-TEE, /// the `/run` directory is mounted in `tmpfs`, which is located in the encrypted memory protected by HW-TEE. /// [`AUTH_FILE_PATH`] shows the path to the `auth.json` file. -pub const AUTH_FILE_PATH: &str = "/run/image-security/auth.json"; +pub const AUTH_FILE_PATH: &str = "kbs:///default/credential/test"; /// `image-rs` configuration information. #[derive(Clone, Debug, Deserialize)] @@ -108,21 +104,12 @@ impl TryFrom<&Path> for ImageConfig { #[derive(Clone, Debug, Deserialize)] pub struct Paths { + /// sigstore config file for simple signing + pub sigstore_config: String, + /// Path to `Policy.json` pub policy_path: String, - /// Dir of `Sigstore Config file`, used by simple signing - pub sig_store_config_dir: String, - - /// Default sigstore config file, used by simple signing - pub default_sig_store_config_file: String, - - /// Path to the gpg pubkey ring of the signature - pub gpg_key_ring: String, - - /// Dir for storage of cosign verification keys - pub cosign_key_dir: String, - /// Path to the auth file pub auth_file: String, } @@ -130,11 +117,8 @@ pub struct Paths { impl Default for Paths { fn default() -> Self { Self { + sigstore_config: SIG_STORE_CONFIG_DEFAULT_FILE.into(), policy_path: POLICY_FILE_PATH.into(), - sig_store_config_dir: SIG_STORE_CONFIG_DIR.into(), - default_sig_store_config_file: SIG_STORE_CONFIG_DEFAULT_FILE.into(), - gpg_key_ring: GPG_KEY_RING.into(), - cosign_key_dir: COSIGN_KEY_DIR.into(), auth_file: AUTH_FILE_PATH.into(), } } @@ -150,10 +134,10 @@ mod tests { #[test] fn test_image_config() { + std::env::remove_var(CC_IMAGE_WORK_DIR); let config = ImageConfig::default(); let work_dir = PathBuf::from(DEFAULT_WORK_DIR); - std::env::remove_var(CC_IMAGE_WORK_DIR); assert_eq!(config.work_dir, work_dir); assert_eq!(config.default_snapshot, SnapshotType::Overlay); diff --git a/src/image.rs b/src/image.rs index e60e0abfd..cb10c4366 100644 --- a/src/image.rs +++ b/src/image.rs @@ -180,48 +180,38 @@ impl ImageClient { // If one of self.config.auth and self.config.security_validate is enabled, // there will establish a secure channel between image-rs and Attestation-Agent #[cfg(feature = "getresource")] - let secure_channel = match self.config.auth || self.config.security_validate { - true => { - // Both we need a [`IMAGE_SECURITY_CONFIG_DIR`] dir - if !Path::new(IMAGE_SECURITY_CONFIG_DIR).exists() { - tokio::fs::create_dir_all(IMAGE_SECURITY_CONFIG_DIR) - .await - .map_err(|e| { - anyhow!("Create image security runtime config dir failed: {:?}", e) - })?; - } + if self.config.auth || self.config.security_validate { + // Both we need a [`IMAGE_SECURITY_CONFIG_DIR`] dir + if !Path::new(IMAGE_SECURITY_CONFIG_DIR).exists() { + tokio::fs::create_dir_all(IMAGE_SECURITY_CONFIG_DIR) + .await + .map_err(|e| { + anyhow!("Create image security runtime config dir failed: {:?}", e) + })?; + } - if let Some(wrapped_aa_kbc_params) = decrypt_config { - let wrapped_aa_kbc_params = wrapped_aa_kbc_params.to_string(); - let aa_kbc_params = - wrapped_aa_kbc_params.trim_start_matches("provider:attestation-agent:"); - - // The secure channel to communicate with KBS. - let secure_channel = Arc::new(Mutex::new( - crate::secure_channel::SecureChannel::new(aa_kbc_params).await?, - )); - Some(secure_channel) - } else { - bail!("Secure channel creation needs aa_kbc_params."); - } + if let Some(wrapped_aa_kbc_params) = decrypt_config { + let wrapped_aa_kbc_params = wrapped_aa_kbc_params.to_string(); + let aa_kbc_params = + wrapped_aa_kbc_params.trim_start_matches("provider:attestation-agent:"); + + // The secure channel to communicate with KBS. + // This step will initialize the secure channel + let mut channel = crate::resource::SECURE_CHANNEL.lock().await; + *channel = Some(crate::resource::kbs::SecureChannel::new(aa_kbc_params).await?); + } else { + bail!("Secure channel creation needs aa_kbc_params."); } - false => None, }; // If no valid auth is given and config.auth is enabled, try to load - // auth from `auth.json`. + // auth from `auth.json` of given place. // If a proper auth is given, use this auth. // If no valid auth is given and config.auth is disabled, use Anonymous auth. - #[cfg(feature = "getresource")] let auth = match (self.config.auth, auth.is_none()) { (true, true) => { - let secure_channel = secure_channel - .as_ref() - .expect("unexpected uninitialized secure channel") - .clone(); match crate::auth::credential_for_reference( &reference, - secure_channel, &self.config.file_paths.auth_file, ) .await @@ -240,27 +230,6 @@ impl ImageClient { _ => auth.expect("unexpected uninitialized auth"), }; - #[cfg(not(feature = "getresource"))] - let auth = match (self.config.auth, auth.is_none()) { - (true, true) => match crate::auth::credential_for_reference_local( - &reference, - &self.config.file_paths.auth_file, - ) - .await - { - Ok(cred) => cred, - Err(e) => { - warn!( - "get credential failed, use Anonymous auth instead: {}", - e.to_string() - ); - RegistryAuth::Anonymous - } - }, - (false, true) => RegistryAuth::Anonymous, - _ => auth.expect("unexpected uninitialized auth"), - }; - let mut client = PullClient::new(reference, &self.config.work_dir.join("layers"), &auth)?; let (image_manifest, image_digest, image_config) = client.pull_manifest().await?; @@ -284,24 +253,7 @@ impl ImageClient { } } - #[cfg(all(feature = "getresource", feature = "signature"))] - if self.config.security_validate { - let secure_channel = secure_channel - .as_ref() - .expect("unexpected uninitialized secure channel") - .clone(); - crate::signature::allows_image( - image_url, - &image_digest, - secure_channel, - &auth, - &self.config.file_paths, - ) - .await - .map_err(|e| anyhow!("Security validate failed: {:?}", e))?; - } - - #[cfg(all(not(feature = "getresource"), feature = "signature"))] + #[cfg(feature = "signature")] if self.config.security_validate { crate::signature::allows_image( image_url, diff --git a/src/lib.rs b/src/lib.rs index 7beaff5d6..557fa0ae1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,8 +13,7 @@ pub mod decrypt; pub mod image; pub mod meta_store; pub mod pull; -#[cfg(feature = "getresource")] -pub mod secure_channel; +pub mod resource; #[cfg(feature = "signature")] pub mod signature; pub mod snapshots; diff --git a/src/secure_channel/grpc.rs b/src/resource/kbs/grpc.rs similarity index 82% rename from src/secure_channel/grpc.rs rename to src/resource/kbs/grpc.rs index 4d4b4c7f0..3427e50a0 100644 --- a/src/secure_channel/grpc.rs +++ b/src/resource/kbs/grpc.rs @@ -38,16 +38,10 @@ impl Grpc { #[async_trait] impl Client for Grpc { - async fn get_resource( - &mut self, - kbc_name: &str, - kbs_uri: &str, - resource_description: String, - ) -> Result> { + async fn get_resource(&mut self, kbc_name: &str, resource_uri: &str) -> Result> { let req = tonic::Request::new(GetResourceRequest { kbc_name: kbc_name.to_string(), - kbs_uri: kbs_uri.to_string(), - resource_description, + resource_uri: resource_uri.to_string(), }); Ok(self.inner.get_resource(req).await?.into_inner().resource) } diff --git a/src/resource/kbs/mod.rs b/src/resource/kbs/mod.rs new file mode 100644 index 000000000..a70432984 --- /dev/null +++ b/src/resource/kbs/mod.rs @@ -0,0 +1,163 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Fetch confidential resources from KBS (Relying Party). + +//! All the fetched resources will be stored in a local filepath: +//! `/run/image-security/kbs/` +//! +//! The `` will be generated by hash256sum the KBS Resource URI/ +//! For example: +//! `kbs://example.org/alice/key/1` will be stored in +//! `/run/image-security/kbs/cde48578964b30b0aa8cecf04c020f64f7cce36fc391b24f45cf8d4e5368e229` + +use std::path::Path; + +#[cfg(not(features = "keywrap-native"))] +use anyhow::Context; +use anyhow::{bail, Result}; +use async_trait::async_trait; +use log::info; +use sha2::{Digest, Sha256}; +use tokio::fs; + +use super::Protocol; + +#[cfg(feature = "keywrap-grpc")] +mod grpc; + +#[cfg(feature = "keywrap-ttrpc")] +mod ttrpc; + +#[cfg(feature = "keywrap-ttrpc")] +mod ttrpc_proto; + +#[cfg(feature = "keywrap-native")] +mod native; + +/// Default workdir to store downloaded kbs resources +const STORAGE_PATH: &str = "/run/image-security/kbs/"; + +/// SecureChannel to connect with KBS +pub struct SecureChannel { + /// Get Resource Service client. + client: Box, + // TODO: now the _kbs_uri from `aa_kbc_params` is not used. Because the + // kbs uri is included in the kbs resource uri. + kbs_uri: String, + kbc_name: String, + /// The path to store downloaded kbs resources + pub storage_path: String, +} + +#[async_trait] +trait Client: Send + Sync { + async fn get_resource(&mut self, kbc_name: &str, resource_uri: &str) -> Result>; +} + +impl SecureChannel { + /// Create a new [`SecureChannel`], the input parameter: + /// * `aa_kbc_params`: s string with format `::`. + pub async fn new(aa_kbc_params: &str) -> Result { + // unzip here is unstable + if let Some((kbc_name, kbs_uri)) = aa_kbc_params.split_once("::") { + if kbc_name.is_empty() { + bail!("aa_kbc_params: missing KBC name"); + } + + if kbs_uri.is_empty() { + bail!("aa_kbc_params: missing KBS URI"); + } + + let client: Box = { + cfg_if::cfg_if! { + if #[cfg(feature = "keywrap-grpc")] { + info!("secure channel uses gRPC"); + Box::new(grpc::Grpc::new().await.context("grpc client init failed")?) + } else if #[cfg(feature = "keywrap-ttrpc")] { + info!("secure channel uses ttrpc"); + Box::new(ttrpc::Ttrpc::new().context("ttrpc client init failed")?) + } else if #[cfg(feature = "keywrap-native")] { + info!("secure channel uses native-aa"); + Box::new(native::Native::default()) + } else { + compile_error!("At last one feature of `keywrap-grpc`, `keywrap-ttrpc`, and `keywrap-native` must be enabled."); + } + } + }; + + fs::create_dir_all(STORAGE_PATH).await?; + + Ok(Self { + client, + kbs_uri: kbs_uri.into(), + kbc_name: kbc_name.into(), + storage_path: STORAGE_PATH.into(), + }) + } else { + bail!("aa_kbc_params: KBC/KBS pair not found") + } + } + + /// Check whether the resource of the uri has been downloaded. + /// Return Some(_) if exists, and return None if not. + async fn check_local(&self, uri: &str) -> Result>> { + let file_path = self.get_filepath(uri); + match Path::new(&file_path).exists() { + true => { + let contents = fs::read(&file_path).await?; + Ok(Some(contents)) + } + false => Ok(None), + } + } + + /// Get the localpath to store the kbs resource in the local filesystem + fn get_filepath(&self, uri: &str) -> String { + let mut sha256 = Sha256::new(); + sha256.update(uri.as_bytes()); + format!("{}/{:x}", self.storage_path, sha256.finalize()) + } + + fn overwrite_kbs_uri(&self, uri: &str) -> Result { + let path = url::Url::parse(uri)?; + Ok(format!("kbs://{}{}", self.kbs_uri, path.path())) + } +} + +#[async_trait] +impl Protocol for SecureChannel { + /// Get resource from using, using `resource_name` as `name` in a ResourceDescription, + /// then save the gathered data into `path` + /// + /// Please refer to https://github.com/confidential-containers/image-rs/blob/main/docs/ccv1_image_security_design.md#get-resource-service + /// for more information. + async fn get_resource(&mut self, resource_uri: &str) -> Result> { + if let Some(res) = self.check_local(resource_uri).await? { + return Ok(res); + } + + // Related issue: https://github.com/confidential-containers/attestation-agent/issues/130 + // + // Now we use `aa_kbc_params` to specify the KBC and KBS URI + // used in CoCo System. Different KBCs are initialized in AA lazily due + // to the kbs uri information included in a `download_confidential_resource` or + // `decrypt_image_layer_annotation`. The kbs uri input to the two APIs + // are from `aa_kbc_params` but not the kbs uri in a resource uri. + // Thus as a temporary solution, we need to overwrite the + // kbs uri field using the one included in `aa_kbc_params`, s.t. + // `kbs_uri` of [`SecureChannel`]. + let processed_resource_uri = self.overwrite_kbs_uri(resource_uri)?; + + let res = self + .client + .get_resource(&self.kbc_name, &processed_resource_uri) + .await?; + + let path = self.get_filepath(resource_uri); + fs::write(path, &res).await?; + Ok(res) + } +} diff --git a/src/secure_channel/native.rs b/src/resource/kbs/native.rs similarity index 63% rename from src/secure_channel/native.rs rename to src/resource/kbs/native.rs index 33a013041..17fe69195 100644 --- a/src/secure_channel/native.rs +++ b/src/resource/kbs/native.rs @@ -19,14 +19,9 @@ pub struct Native { #[async_trait] impl Client for Native { - async fn get_resource( - &mut self, - kbc_name: &str, - kbs_uri: &str, - resource_description: String, - ) -> Result> { + async fn get_resource(&mut self, kbc_name: &str, resource_uri: &str) -> Result> { self.inner - .download_confidential_resource(kbc_name, kbs_uri, &resource_description) + .download_confidential_resource(kbc_name, resource_uri) .await } } diff --git a/src/secure_channel/ttrpc.rs b/src/resource/kbs/ttrpc.rs similarity index 78% rename from src/secure_channel/ttrpc.rs rename to src/resource/kbs/ttrpc.rs index 20a2680c2..6d3749a8e 100644 --- a/src/secure_channel/ttrpc.rs +++ b/src/resource/kbs/ttrpc.rs @@ -31,16 +31,10 @@ impl Ttrpc { #[async_trait] impl Client for Ttrpc { - async fn get_resource( - &mut self, - kbc_name: &str, - kbs_uri: &str, - resource_description: String, - ) -> Result> { + async fn get_resource(&mut self, kbc_name: &str, resource_uri: &str) -> Result> { let req = GetResourceRequest { - KbcName: kbc_name.into(), - KbsUri: kbs_uri.into(), - ResourceDescription: resource_description, + KbcName: kbc_name.to_string(), + ResourceUri: resource_uri.to_string(), ..Default::default() }; let res = self diff --git a/src/secure_channel/ttrpc_proto/getresource.rs b/src/resource/kbs/ttrpc_proto/getresource.rs similarity index 83% rename from src/secure_channel/ttrpc_proto/getresource.rs rename to src/resource/kbs/ttrpc_proto/getresource.rs index f3e04c7c2..1566bfcac 100644 --- a/src/secure_channel/ttrpc_proto/getresource.rs +++ b/src/resource/kbs/ttrpc_proto/getresource.rs @@ -29,12 +29,10 @@ const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_2_0; // @@protoc_insertion_point(message:getresource.GetResourceRequest) pub struct GetResourceRequest { // message fields + // @@protoc_insertion_point(field:getresource.GetResourceRequest.ResourceUri) + pub ResourceUri: ::std::string::String, // @@protoc_insertion_point(field:getresource.GetResourceRequest.KbcName) pub KbcName: ::std::string::String, - // @@protoc_insertion_point(field:getresource.GetResourceRequest.KbsUri) - pub KbsUri: ::std::string::String, - // @@protoc_insertion_point(field:getresource.GetResourceRequest.ResourceDescription) - pub ResourceDescription: ::std::string::String, // special fields // @@protoc_insertion_point(special_field:getresource.GetResourceRequest.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -52,23 +50,18 @@ impl GetResourceRequest { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(3); + let mut fields = ::std::vec::Vec::with_capacity(2); let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "ResourceUri", + |m: &GetResourceRequest| { &m.ResourceUri }, + |m: &mut GetResourceRequest| { &mut m.ResourceUri }, + )); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "KbcName", |m: &GetResourceRequest| { &m.KbcName }, |m: &mut GetResourceRequest| { &mut m.KbcName }, )); - fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "KbsUri", - |m: &GetResourceRequest| { &m.KbsUri }, - |m: &mut GetResourceRequest| { &mut m.KbsUri }, - )); - fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( - "ResourceDescription", - |m: &GetResourceRequest| { &m.ResourceDescription }, - |m: &mut GetResourceRequest| { &mut m.ResourceDescription }, - )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "GetResourceRequest", fields, @@ -88,13 +81,10 @@ impl ::protobuf::Message for GetResourceRequest { while let Some(tag) = is.read_raw_tag_or_eof()? { match tag { 10 => { - self.KbcName = is.read_string()?; + self.ResourceUri = is.read_string()?; }, 18 => { - self.KbsUri = is.read_string()?; - }, - 26 => { - self.ResourceDescription = is.read_string()?; + self.KbcName = is.read_string()?; }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; @@ -108,14 +98,11 @@ impl ::protobuf::Message for GetResourceRequest { #[allow(unused_variables)] fn compute_size(&self) -> u64 { let mut my_size = 0; - if !self.KbcName.is_empty() { - my_size += ::protobuf::rt::string_size(1, &self.KbcName); + if !self.ResourceUri.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.ResourceUri); } - if !self.KbsUri.is_empty() { - my_size += ::protobuf::rt::string_size(2, &self.KbsUri); - } - if !self.ResourceDescription.is_empty() { - my_size += ::protobuf::rt::string_size(3, &self.ResourceDescription); + if !self.KbcName.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.KbcName); } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); @@ -123,14 +110,11 @@ impl ::protobuf::Message for GetResourceRequest { } fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { - if !self.KbcName.is_empty() { - os.write_string(1, &self.KbcName)?; - } - if !self.KbsUri.is_empty() { - os.write_string(2, &self.KbsUri)?; + if !self.ResourceUri.is_empty() { + os.write_string(1, &self.ResourceUri)?; } - if !self.ResourceDescription.is_empty() { - os.write_string(3, &self.ResourceDescription)?; + if !self.KbcName.is_empty() { + os.write_string(2, &self.KbcName)?; } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) @@ -149,17 +133,15 @@ impl ::protobuf::Message for GetResourceRequest { } fn clear(&mut self) { + self.ResourceUri.clear(); self.KbcName.clear(); - self.KbsUri.clear(); - self.ResourceDescription.clear(); self.special_fields.clear(); } fn default_instance() -> &'static GetResourceRequest { static instance: GetResourceRequest = GetResourceRequest { + ResourceUri: ::std::string::String::new(), KbcName: ::std::string::String::new(), - KbsUri: ::std::string::String::new(), - ResourceDescription: ::std::string::String::new(), special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -306,13 +288,12 @@ impl ::protobuf::reflect::ProtobufValue for GetResourceResponse { } static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x11getresource.proto\x12\x0bgetresource\"x\n\x12GetResourceRequest\ - \x12\x18\n\x07KbcName\x18\x01\x20\x01(\tR\x07KbcName\x12\x16\n\x06KbsUri\ - \x18\x02\x20\x01(\tR\x06KbsUri\x120\n\x13ResourceDescription\x18\x03\x20\ - \x01(\tR\x13ResourceDescription\"1\n\x13GetResourceResponse\x12\x1a\n\ - \x08Resource\x18\x01\x20\x01(\x0cR\x08Resource2f\n\x12GetResourceService\ - \x12P\n\x0bGetResource\x12\x1f.getresource.GetResourceRequest\x1a\x20.ge\ - tresource.GetResourceResponseb\x06proto3\ + \n\x11getresource.proto\x12\x0bgetresource\"P\n\x12GetResourceRequest\ + \x12\x20\n\x0bResourceUri\x18\x01\x20\x01(\tR\x0bResourceUri\x12\x18\n\ + \x07KbcName\x18\x02\x20\x01(\tR\x07KbcName\"1\n\x13GetResourceResponse\ + \x12\x1a\n\x08Resource\x18\x01\x20\x01(\x0cR\x08Resource2f\n\x12GetResou\ + rceService\x12P\n\x0bGetResource\x12\x1f.getresource.GetResourceRequest\ + \x1a\x20.getresource.GetResourceResponseb\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file diff --git a/src/secure_channel/ttrpc_proto/getresource_ttrpc.rs b/src/resource/kbs/ttrpc_proto/getresource_ttrpc.rs similarity index 100% rename from src/secure_channel/ttrpc_proto/getresource_ttrpc.rs rename to src/resource/kbs/ttrpc_proto/getresource_ttrpc.rs diff --git a/src/secure_channel/ttrpc_proto/mod.rs b/src/resource/kbs/ttrpc_proto/mod.rs similarity index 100% rename from src/secure_channel/ttrpc_proto/mod.rs rename to src/resource/kbs/ttrpc_proto/mod.rs diff --git a/src/resource/mod.rs b/src/resource/mod.rs new file mode 100644 index 000000000..923d4e6a9 --- /dev/null +++ b/src/resource/mod.rs @@ -0,0 +1,78 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! This module helps to fetch resource using different +//! protocols. Different resources can be marked in a +//! specific uri. Now, it supports the following: +//! +//! - `file://`: from the local filesystem +//! - `kbs://`: using secure channel to fetch from the KBS + +use anyhow::*; +use async_trait::async_trait; +use tokio::fs; + +#[cfg(feature = "getresource")] +pub mod kbs; + +#[cfg(feature = "getresource")] +lazy_static::lazy_static! { + /// SecureChannel + pub static ref SECURE_CHANNEL: tokio::sync::Mutex> = { + tokio::sync::Mutex::new(None) + }; +} + +/// A protocol should implement this trait. For example, +/// a `file://` scheme's +/// - `SCHEME`: `file` string, to distinguish different uri scheme +/// - `get_resource()`: get resource from the uri +#[async_trait] +trait Protocol: Send + Sync { + async fn get_resource(&mut self, uri: &str) -> Result>; +} + +/// This is a public API to retrieve resources. The input parameter `uri` should be +/// a URL. For example `file://...` +/// The resource will be retrieved in different ways due to different schemes. +/// If no scheme is given, it will by default use `file://` to look for the file +/// in the local filesystem. +pub async fn get_resource(uri: &str) -> Result> { + let uri = if uri.contains("://") { + uri.to_string() + } else { + "file://".to_owned() + uri + }; + + let url = url::Url::parse(&uri).map_err(|e| anyhow!("Failed to parse: {:?}", e))?; + match url.scheme() { + "kbs" => { + #[cfg(feature = "getresource")] + { + SECURE_CHANNEL + .lock() + .await + .as_mut() + .ok_or_else(|| anyhow!("Uninitialized secure channel"))? + .get_resource(&uri) + .await + } + + #[cfg(not(feature = "getresource"))] + { + bail!( + "`getresource` feature not enabled, cannot support fetch resource uri {}", + uri + ) + } + } + "file" => { + let path = url.path(); + let content = fs::read(path).await?; + Ok(content) + } + others => bail!("not support scheme {}", others), + } +} diff --git a/src/secure_channel/mod.rs b/src/secure_channel/mod.rs deleted file mode 100644 index d020ac9d0..000000000 --- a/src/secure_channel/mod.rs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2023 Alibaba Cloud -// -// SPDX-License-Identifier: Apache-2.0 -// - -//! Fetch confidential resources from KBS (Relying Party). - -use std::collections::HashMap; - -#[cfg(not(features = "keywrap-native"))] -use anyhow::Context; -use anyhow::{bail, Result}; - -use async_trait::async_trait; -use serde::{Deserialize, Serialize}; -use tokio::fs; - -#[cfg(feature = "keywrap-grpc")] -mod grpc; - -#[cfg(feature = "keywrap-ttrpc")] -mod ttrpc; - -#[cfg(feature = "keywrap-ttrpc")] -mod ttrpc_proto; - -#[cfg(feature = "keywrap-native")] -mod native; - -#[cfg(any( - not(any( - feature = "keywrap-grpc", - feature = "keywrap-ttrpc", - feature = "keywrap-native" - )), - all( - feature = "keywrap-grpc", - any(feature = "keywrap-ttrpc", feature = "keywrap-native") - ), - all( - feature = "keywrap-ttrpc", - any(feature = "keywrap-grpc", feature = "keywrap-native") - ), - all( - feature = "keywrap-native", - any(feature = "keywrap-grpc", feature = "keywrap-ttrpc") - ), -))] -compile_error!("One and exactly one feature of `keywrap-grpc`, `keywrap-ttrpc`, and `keywrap-native` must be enabled."); - -/// The resource description that will be passed to AA when get resource. -#[derive(Serialize, Deserialize, Debug)] -struct ResourceDescription { - name: String, - optional: HashMap, -} - -impl ResourceDescription { - /// Create a new ResourceDescription with resource name. - pub fn new(name: &str, optional: HashMap) -> Self { - ResourceDescription { - name: name.to_string(), - optional, - } - } -} - -/// SecureChannel to connect with KBS -pub struct SecureChannel { - /// Get Resource Service client. - client: Box, - kbc_name: String, - kbs_uri: String, -} - -#[async_trait] -pub trait Client: Send + Sync { - async fn get_resource( - &mut self, - kbc_name: &str, - kbs_uri: &str, - resource_description: String, - ) -> Result>; -} - -impl SecureChannel { - /// Create a new [`SecureChannel`], the input parameter: - /// * `aa_kbc_params`: s string with format `::`. - pub async fn new(aa_kbc_params: &str) -> Result { - // unzip here is unstable - if let Some((kbc_name, kbs_uri)) = aa_kbc_params.split_once("::") { - if kbc_name.is_empty() { - bail!("aa_kbc_params: missing KBC name"); - } - - if kbs_uri.is_empty() { - bail!("aa_kbc_params: missing KBS URI"); - } - - let client: Box = { - #[cfg(feature = "keywrap-grpc")] - { - Box::new(grpc::Grpc::new().await.context("grpc client init failed")?) - } - - #[cfg(feature = "keywrap-ttrpc")] - { - Box::new(ttrpc::Ttrpc::new().context("ttrpc client init failed")?) - } - - #[cfg(feature = "keywrap-native")] - { - Box::new(native::Native::default()) - } - }; - - Ok(Self { - client, - kbc_name: kbc_name.into(), - kbs_uri: kbs_uri.into(), - }) - } else { - bail!("aa_kbc_params: KBC/KBS pair not found") - } - } - - /// Get resource from using, using `resource_name` as `name` in a ResourceDescription, - /// then save the gathered data into `path` - /// - /// Please refer to https://github.com/confidential-containers/image-rs/blob/main/docs/ccv1_image_security_design.md#get-resource-service - /// for more information. - pub async fn get_resource( - &mut self, - resource_name: &str, - optional: HashMap, - path: &str, - ) -> Result<()> { - let resource_description = - serde_json::to_string(&ResourceDescription::new(resource_name, optional))?; - let res = self - .client - .get_resource(&self.kbc_name, &self.kbs_uri, resource_description) - .await?; - fs::write(path, res).await?; - Ok(()) - } -} diff --git a/src/signature/README.md b/src/signature/README.md index 569668e1b..ec6ff81eb 100644 --- a/src/signature/README.md +++ b/src/signature/README.md @@ -75,21 +75,6 @@ pub trait SignScheme { /// * gathering necessary files. async fn init(&self) -> Result<()>; - /// Reture a HashMap including a resource's name => file path in fs. - /// - /// Here `resource's name` is the `name` field for a ResourceDescription - /// in GetResourceRequest. - /// Please refer to https://github.com/confidential-containers/image-rs/blob/main/docs/ccv1_image_security_design.md#get-resource-service - /// for more information about the `GetResourceRequest`. - /// - /// This function will be called by `Agent`, to get the manifest - /// of all the resources to be gathered from kbs. The gathering - /// operation will happen after `init_scheme()`, to prepare necessary - /// resources. The HashMap here uses &str rather than String, - /// which encourages developer of new signing schemes to define - /// const &str for these information. - fn resource_manifest(&self) -> HashMap<&str, &str>; - /// Judge whether an image is allowed by this SignScheme. async fn allows_image(&self, image: &mut Image) -> Result<()>; } @@ -133,16 +118,7 @@ It can do initialization work for this scheme. This may include the following * preparing runtime directories for storing signatures, configurations, etc. * gathering necessary files. -2. `resource_manifest()`: This function will tell the `Agent` -which resources it need to retrieve from the kbs. The return value should be -a HashMap. The key of the HashMap is the `name` field for a ResourceDescription -in GetResourceRequest. The value is the file path that the returned resource will be -written into after retrieving the resource. Refer to -[get-resource-service](https://github.com/confidential-containers/image-rs/blob/main/docs/ccv1_image_security_design.md#get-resource-service) -for more information about GetResourceRequest. This function will be called -on every check for a Policy Requirement of this signing scheme. - -3. `allows_image()`: This function will do the verification. This +2. `allows_image()`: This function will do the verification. This function will be called on every check for a Policy Requirement of this signing scheme. ### `src/policy/policy_requirement.rs` diff --git a/src/signature/mechanism/cosign/mod.rs b/src/signature/mechanism/cosign/mod.rs index 78608b800..7d4249ee3 100644 --- a/src/signature/mechanism/cosign/mod.rs +++ b/src/signature/mechanism/cosign/mod.rs @@ -5,8 +5,6 @@ //! Cosign verification -use std::{collections::HashMap, path::Path}; - use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; use oci_distribution::secrets::RegistryAuth; @@ -22,12 +20,12 @@ use sigstore::{ errors::SigstoreVerifyConstraintsError, registry::Auth, }; -use tokio::fs; use super::SignScheme; -use crate::config::Paths; +use crate::resource; use crate::signature::{ - image::Image, payload::simple_signing::SigPayload, policy::ref_match::PolicyReqMatchType, + image::Image, mechanism::Paths, payload::simple_signing::SigPayload, + policy::ref_match::PolicyReqMatchType, }; /// The name of resource to request cosign verification key from kbs @@ -54,10 +52,6 @@ pub struct CosignParameters { // This field is optional. #[serde(default, rename = "signedIdentity")] pub signed_identity: Option, - - /// Dir for storage of cosign verification keys - #[serde(skip)] - pub cosign_key_dir: String, } #[async_trait] @@ -65,40 +59,15 @@ impl SignScheme for CosignParameters { /// This initialization will: /// * Create [`COSIGN_KEY_DIR`] if not exist. #[cfg(feature = "signature-cosign")] - async fn init(&mut self, config: &Paths) -> Result<()> { - self.cosign_key_dir = config.cosign_key_dir.clone(); - if !Path::new(&self.cosign_key_dir).exists() { - fs::create_dir_all(&self.cosign_key_dir) - .await - .map_err(|e| { - anyhow!("Create Simple Signing sigstore-config dir failed: {:?}", e) - })?; - } - + async fn init(&mut self, _config: &Paths) -> Result<()> { Ok(()) } #[cfg(not(feature = "signature-cosign"))] - async fn init(&mut self, config: &Paths) -> Result<()> { + async fn init(&mut self, _config: &Paths) -> Result<()> { Ok(()) } - #[cfg(feature = "signature-cosign")] - fn resource_manifest(&self) -> HashMap<&str, &str> { - let mut manifest_from_kbs = HashMap::new(); - if let Some(key_path) = &self.key_path { - if !Path::new(key_path).exists() { - manifest_from_kbs.insert(COSIGN_KEY_KBS, &key_path[..]); - } - } - manifest_from_kbs - } - - #[cfg(not(feature = "signature-cosign"))] - fn resource_manifest(&self) -> HashMap<&str, &str> { - HashMap::new() - } - /// Judge whether an image is allowed by this SignScheme. #[cfg(feature = "signature-cosign")] async fn allows_image(&self, image: &mut Image, auth: &RegistryAuth) -> Result<()> { @@ -160,7 +129,7 @@ impl CosignParameters { // Get the pubkey let key = match (&self.key_data, &self.key_path) { (None, None) => bail!("Neither keyPath nor keyData is specified."), - (None, Some(key_path)) => read_key_from(key_path).await?, + (None, Some(key_path)) => resource::get_resource(key_path).await?, (Some(key_data), None) => key_data.as_bytes().to_vec(), (Some(_), Some(_)) => bail!("Both keyPath and keyData are specified."), }; @@ -201,15 +170,6 @@ impl CosignParameters { } } -async fn read_key_from(path: &str) -> Result> { - // TODO: Do we need define a new URL scheme - // named `kbs://` to indicate that the key - // should be got from kbs? This would be - // helpful for issue - // - Ok(fs::read(path).await?) -} - #[cfg(feature = "signature-cosign")] #[cfg(test)] mod tests { @@ -233,28 +193,49 @@ mod tests { #[rstest] #[case( CosignParameters{ - key_path: Some("test_data/signature/cosign/cosign1.pub".into()), + key_path: Some( + format!( + "{}/test_data/signature/cosign/cosign1.pub", + std::env::current_dir() + .expect("get current dir") + .to_str() + .expect("get current dir"), + ) + ), key_data: None, signed_identity: None, - cosign_key_dir: crate::config::COSIGN_KEY_DIR.into(), }, "registry.cn-hangzhou.aliyuncs.com/xynnn/cosign:latest", )] #[case( CosignParameters{ - key_path: Some("test_data/signature/cosign/cosign1.pub".into()), + key_path: Some( + format!( + "{}/test_data/signature/cosign/cosign1.pub", + std::env::current_dir() + .expect("get current dir") + .to_str() + .expect("get current dir"), + ) + ), key_data: None, signed_identity: None, - cosign_key_dir: crate::config::COSIGN_KEY_DIR.into(), }, "registry-1.docker.io/xynnn007/cosign:latest", )] #[case( CosignParameters{ - key_path: Some("test_data/signature/cosign/cosign1.pub".into()), + key_path: Some( + format!( + "{}/test_data/signature/cosign/cosign1.pub", + std::env::current_dir() + .expect("get current dir") + .to_str() + .expect("get current dir"), + ) + ), key_data: None, signed_identity: None, - cosign_key_dir: crate::config::COSIGN_KEY_DIR.into(), }, "quay.io/kata-containers/confidential-containers:cosign-signed", )] @@ -270,17 +251,18 @@ mod tests { image .set_manifest_digest(IMAGE_DIGEST) .expect("Set manifest digest failed."); + let res = parameter + .verify_signature_and_get_payload( + &mut image, + &oci_distribution::secrets::RegistryAuth::Anonymous, + ) + .await; assert!( - parameter - .verify_signature_and_get_payload( - &mut image, - &oci_distribution::secrets::RegistryAuth::Anonymous - ) - .await - .is_ok(), - "failed test:\nparameter:{:?}\nimage reference:{}", + res.is_ok(), + "failed test:\nparameter: {:?}\nimage reference: {}\nreason: {:?}", parameter, - image_reference + image_reference, + res, ); } @@ -299,82 +281,99 @@ mod tests { key_path: None, key_data: None, signed_identity: Some(policy_match), - cosign_key_dir: crate::config::COSIGN_KEY_DIR.into(), }; assert_eq!(parameter.check_reference_rule_types().is_ok(), pass); } #[rstest] #[case( - r#"{ - "type": "sigstoreSigned", - "keyPath": "test_data/signature/cosign/cosign2.pub" - }"#, + &format!("\ + {{\ + \"type\": \"sigstoreSigned\",\ + \"keyPath\": \"{}/test_data/signature/cosign/cosign2.pub\"\ + }}", + std::env::current_dir().expect("get current dir").to_str().expect("get current dir") + ), "registry.cn-hangzhou.aliyuncs.com/xynnn/cosign:latest", false, // If verified failed, the pubkey given to verify will be printed. "[PublicKeyVerifier { key: CosignVerificationKey { verification_algorithm: ECDSA_P256_SHA256_ASN1, data: [4, 192, 146, 124, 21, 74, 44, 46, 129, 189, 211, 135, 35, 87, 145, 71, 172, 25, 92, 98, 102, 245, 109, 29, 191, 50, 55, 236, 233, 47, 136, 66, 124, 253, 181, 135, 68, 180, 68, 84, 60, 97, 97, 147, 39, 218, 80, 228, 49, 224, 66, 101, 2, 236, 78, 109, 162, 5, 171, 119, 141, 234, 112, 247, 247] } }]" )] #[case( - r#"{ - "type": "sigstoreSigned", - "keyPath": "test_data/signature/cosign/cosign1.pub", - "signedIdentity": { - "type": "exactRepository", - "dockerRepository": "registry-1.docker.io/xynnn007/cosign-err" - } - }"#, + &format!("\ + {{\ + \"type\": \"sigstoreSigned\",\ + \"keyPath\": \"{}/test_data/signature/cosign/cosign1.pub\",\ + \"signedIdentity\": {{\ + \"type\": \"exactRepository\",\ + \"dockerRepository\": \"registry-1.docker.io/xynnn007/cosign-err\"\ + }}\ + }}", + std::env::current_dir().expect("get current dir").to_str().expect("get current dir") + ), // The repository of the given image's and the Payload's are different "registry-1.docker.io/xynnn007/cosign:latest", false, "Match reference failed.", )] #[case( - r#"{ - "type": "sigstoreSigned", - "keyPath": "test_data/signature/cosign/cosign2.pub" - }"#, + &format!("\ + {{\ + \"type\": \"sigstoreSigned\",\ + \"keyPath\": \"{}/test_data/signature/cosign/cosign2.pub\"\ + }}", + std::env::current_dir().expect("get current dir").to_str().expect("get current dir") + ), "quay.io/kata-containers/confidential-containers:cosign-signed", false, // If verified failed, the pubkey given to verify will be printed. "[PublicKeyVerifier { key: CosignVerificationKey { verification_algorithm: ECDSA_P256_SHA256_ASN1, data: [4, 192, 146, 124, 21, 74, 44, 46, 129, 189, 211, 135, 35, 87, 145, 71, 172, 25, 92, 98, 102, 245, 109, 29, 191, 50, 55, 236, 233, 47, 136, 66, 124, 253, 181, 135, 68, 180, 68, 84, 60, 97, 97, 147, 39, 218, 80, 228, 49, 224, 66, 101, 2, 236, 78, 109, 162, 5, 171, 119, 141, 234, 112, 247, 247] } }]", )] #[case( - r#"{ - "type": "sigstoreSigned", - "keyPath": "test_data/signature/cosign/cosign1.pub", - "signedIdentity": { - "type" : "matchExact" - } - }"#, + &format!("\ + {{\ + \"type\": \"sigstoreSigned\",\ + \"keyPath\": \"{}/test_data/signature/cosign/cosign1.pub\",\ + \"signedIdentity\": {{\ + \"type\": \"matchExact\"\ + }}\ + }}", + std::env::current_dir().expect("get current dir").to_str().expect("get current dir") + ), "quay.io/kata-containers/confidential-containers:cosign-signed", false, // Only MatchRepository and ExactRepository are supported. "Denied by MatchExact", )] #[case( - r#"{ - "type": "sigstoreSigned", - "keyPath": "test_data/signature/cosign/cosign1.pub" - }"#, + &format!("\ + {{\ + \"type\": \"sigstoreSigned\",\ + \"keyPath\": \"{}/test_data/signature/cosign/cosign1.pub\"\ + }}", + std::env::current_dir().expect("get current dir").to_str().expect("get current dir")), "registry.cn-hangzhou.aliyuncs.com/xynnn/cosign:signed", true, "" )] #[case( - r#"{ - "type": "sigstoreSigned", - "keyPath": "test_data/signature/cosign/cosign1.pub" - }"#, + &format!("\ + {{\ + \"type\": \"sigstoreSigned\",\ + \"keyPath\": \"{}/test_data/signature/cosign/cosign1.pub\"\ + }}", + std::env::current_dir().expect("get current dir").to_str().expect("get current dir")), "registry-1.docker.io/xynnn007/cosign:latest", true, "" )] #[case( - r#"{ - "type": "sigstoreSigned", - "keyPath": "test_data/signature/cosign/cosign1.pub" - }"#, + &format!("\ + {{\ + \"type\": \"sigstoreSigned\",\ + \"keyPath\": \"{}/test_data/signature/cosign/cosign1.pub\"\ + }}", + std::env::current_dir().expect("get current dir").to_str().expect("get current dir")), "quay.io/kata-containers/confidential-containers:cosign-signed", true, "" diff --git a/src/signature/mechanism/mod.rs b/src/signature/mechanism/mod.rs index cb029cd6e..1d1121ae2 100644 --- a/src/signature/mechanism/mod.rs +++ b/src/signature/mechanism/mod.rs @@ -14,8 +14,6 @@ //! schemes, we use a trait [`SignScheme`] to define. The trait object //! will be included into [`crate::policy::PolicyReqType`]. -use std::collections::HashMap; - use anyhow::*; use async_trait::async_trait; use oci_distribution::secrets::RegistryAuth; @@ -35,21 +33,6 @@ pub trait SignScheme: Send + Sync { /// * gathering necessary files. async fn init(&mut self, config: &Paths) -> Result<()>; - /// Reture a HashMap including a resource's name => file path in fs. - /// - /// Here `resource's name` is the `name` field for a ResourceDescription - /// in GetResourceRequest. - /// Please refer to - /// for more information about the `GetResourceRequest`. - /// - /// This function will be called by `Agent`, to get the manifest - /// of all the resources to be gathered from kbs. The gathering - /// operation will happen after `init_scheme()`, to prepare necessary - /// resources. The HashMap here uses &str rather than String, - /// which encourages developer of new signing schemes to define - /// const &str for these information. - fn resource_manifest(&self) -> HashMap<&str, &str>; - /// Judge whether an image is allowed by this SignScheme. async fn allows_image(&self, image: &mut Image, auth: &RegistryAuth) -> Result<()>; } diff --git a/src/signature/mechanism/simple/README.md b/src/signature/mechanism/simple/README.md index a98d80429..85c61ccba 100644 --- a/src/signature/mechanism/simple/README.md +++ b/src/signature/mechanism/simple/README.md @@ -46,12 +46,10 @@ Let's see what the code do here: 1. `init()` will check and create the directory * Sigstore Dir: `/run/image-security/simple_signing/sigstore_config` -2. Then the following files will be got from kbs -* Sigstore Configfile: `/run/image-security/simple_signing/sigstore_config/default.yaml`. This file shows where the signatures are stored. -* Gpg public key ring: `/run/image-security/simple_signing/pubkey.gpg`. This key -ring is used to verify signatures. +2. Where an image's related signature is stored is defined in `Sigstore Configfile`. The uri of the `Sigstore Configfile` will be defined in `ImageClient.config.file_paths.sigstore_config`. +The `Sigstore Configfile` will be fetched. -3. Then access the Sigstore, and gather the signatures related to the image, and +3. Then access the `Sigstore Configfile`, and gather the signatures related to the image, and do verifications. ## KBS ResourceDescription diff --git a/src/signature/mechanism/simple/mod.rs b/src/signature/mechanism/simple/mod.rs index ad2e452eb..7bbb6fe5a 100644 --- a/src/signature/mechanism/simple/mod.rs +++ b/src/signature/mechanism/simple/mod.rs @@ -7,7 +7,6 @@ use anyhow::*; use async_trait::async_trait; use oci_distribution::secrets::RegistryAuth; use serde::*; -use std::collections::HashMap; use strum_macros::Display; use strum_macros::EnumString; @@ -16,18 +15,10 @@ mod sigstore; #[cfg(feature = "signature-simple")] mod verify; -use crate::config::Paths; -use crate::signature::image::Image; -use crate::signature::policy::ref_match::PolicyReqMatchType; +use crate::signature::{image::Image, mechanism::Paths, policy::ref_match::PolicyReqMatchType}; use super::SignScheme; -/// The name of resource to request sigstore config from kbs -pub const SIG_STORE_CONFIG_KBS: &str = "Sigstore Config"; - -/// The name of gpg key ring to request sigstore config from kbs -pub const GPG_KEY_RING_KBS: &str = "GPG Keyring"; - #[derive(Deserialize, Debug, PartialEq, Eq, Serialize, Default)] pub struct SimpleParameters { // KeyType specifies what kind of the public key to verify the signatures. @@ -54,17 +45,10 @@ pub struct SimpleParameters { #[serde(default, rename = "signedIdentity")] pub signed_identity: Option, - /// Dir of `Sigstore Config file` - #[serde(skip)] - pub sig_store_config_dir: String, - - /// Default sigstore config file - #[serde(skip)] - pub default_sig_store_config_file: String, - - /// Path to the gpg pubkey ring of the signature + /// Sigstore config file + #[cfg(feature = "signature-simple")] #[serde(skip)] - pub gpg_key_ring: String, + pub(crate) sig_store_config_file: sigstore::SigstoreConfig, } /// Prepare directories for configs and sigstore configs. @@ -85,11 +69,13 @@ impl SignScheme for SimpleParameters { /// Init simple scheme signing #[cfg(feature = "signature-simple")] async fn init(&mut self, config: &Paths) -> Result<()> { - self.sig_store_config_dir = config.sig_store_config_dir.clone(); - self.default_sig_store_config_file = config.default_sig_store_config_file.clone(); - self.gpg_key_ring = config.gpg_key_ring.clone(); - prepare_runtime_dirs(&self.sig_store_config_dir).await?; - + prepare_runtime_dirs(crate::config::SIG_STORE_CONFIG_DIR).await?; + self.initialize_sigstore_config().await?; + let sig_store_config_file = crate::resource::get_resource(&config.sigstore_config).await?; + let sig_store_config_file = + serde_yaml::from_slice::(&sig_store_config_file)?; + self.sig_store_config_file + .update_self(sig_store_config_file)?; Ok(()) } @@ -98,33 +84,6 @@ impl SignScheme for SimpleParameters { Ok(()) } - /// Check whether [`SIG_STORE_CONFIG_DIR`] and [`GPG_KEY_RING`] exist. - #[cfg(feature = "signature-simple")] - fn resource_manifest(&self) -> HashMap<&str, &str> { - let mut res = HashMap::<&str, &str>::new(); - - // Sigstore Config - if std::path::PathBuf::from(&self.sig_store_config_dir) - .read_dir() - .map(|mut i| i.next().is_none()) - .unwrap_or(false) - { - res.insert(SIG_STORE_CONFIG_KBS, &self.default_sig_store_config_file); - } - - // gpg key ring - if !std::path::Path::new(&self.gpg_key_ring).exists() { - res.insert(GPG_KEY_RING_KBS, &self.gpg_key_ring); - } - - res - } - - #[cfg(not(feature = "signature-simple"))] - fn resource_manifest(&self) -> HashMap<&str, &str> { - HashMap::new() - } - #[cfg(feature = "signature-simple")] async fn allows_image(&self, image: &mut Image, _auth: &RegistryAuth) -> Result<()> { // FIXME: only support "GPGKeys" type now. @@ -141,19 +100,21 @@ impl SignScheme for SimpleParameters { (None, None) => bail!("Neither keyPath or keyData specified."), (Some(_), Some(_)) => bail!("Both keyPath and keyData specified."), (None, Some(key_data)) => base64::decode(key_data)?, - (Some(key_path), None) => tokio::fs::read(key_path).await.map_err(|e| { - anyhow!("Read SignedBy keyPath failed: {:?}, path: {}", e, key_path) - })?, + (Some(key_path), None) => { + crate::resource::get_resource(key_path).await.map_err(|e| { + anyhow!("Read SignedBy keyPath failed: {:?}, path: {}", e, key_path) + })? + } }; - let sigs = get_signatures(image).await?; + let sigs = self.get_signatures(image).await?; let mut reject_reasons: Vec = Vec::new(); for sig in sigs.iter() { match judge_single_signature( image, self.signed_identity.as_ref(), - pubkey_ring.clone(), + &pubkey_ring, sig.to_vec(), ) { // One accepted signature is enough. @@ -192,7 +153,7 @@ pub enum KeyType { pub fn judge_single_signature( image: &Image, signed_identity: Option<&PolicyReqMatchType>, - pubkey_ring: Vec, + pubkey_ring: &[u8], sig: Vec, ) -> Result<()> { // Verify the signature with the pubkey ring. @@ -216,40 +177,50 @@ pub fn judge_single_signature( } #[cfg(feature = "signature-simple")] -pub async fn get_signatures(image: &mut Image) -> Result>> { - use std::convert::TryInto; - // Get image digest (manifest digest) - let image_digest = if !image.manifest_digest.is_empty() { - image.manifest_digest.clone() - } else if let Some(d) = image.reference.digest() { - d.try_into()? - } else { - bail!("Missing image digest"); - }; +impl SimpleParameters { + /// Set the content of sigstore config with files in + /// [`crate::config::SIG_STORE_CONFIG_DIR`] + pub async fn initialize_sigstore_config(&mut self) -> Result<()> { + // If the registry support `X-Registry-Supports-Signatures` API extension, + // try to get signatures from the registry first. + // Else, get signatures from "sigstore" according to the sigstore config file. + // (https://github.com/containers/image/issues/384) + // + // TODO: Add get signatures from registry X-R-S-S API extension. + // + // issue: https://github.com/confidential-containers/image-rs/issues/12 + let sigstore_config = + sigstore::SigstoreConfig::new_from_configs(crate::config::SIG_STORE_CONFIG_DIR).await?; + self.sig_store_config_file.update_self(sigstore_config)?; - // Format the sigstore name: `image-repository@digest-algorithm=digest-value`. - let sigstore_name = sigstore::format_sigstore_name(&image.reference, image_digest); + Ok(()) + } - // If the registry support `X-Registry-Supports-Signatures` API extension, - // try to get signatures from the registry first. - // Else, get signatures from "sigstore" according to the sigstore config file. - // (https://github.com/containers/image/issues/384) - // - // TODO: Add get signatures from registry X-R-S-S API extension. - // - // issue: https://github.com/confidential-containers/image-rs/issues/12 - let sigstore_config = - sigstore::SigstoreConfig::new_from_configs(crate::config::SIG_STORE_CONFIG_DIR).await?; + pub async fn get_signatures(&self, image: &mut Image) -> Result>> { + use std::convert::TryInto; + // Get image digest (manifest digest) + let image_digest = if !image.manifest_digest.is_empty() { + image.manifest_digest.clone() + } else if let Some(d) = image.reference.digest() { + d.try_into()? + } else { + bail!("Missing image digest"); + }; + + // Format the sigstore name: `image-repository@digest-algorithm=digest-value`. + let sigstore_name = sigstore::format_sigstore_name(&image.reference, image_digest); - let sigstore_base_url = sigstore_config - .base_url(&image.reference)? - .ok_or_else(|| anyhow!("The sigstore base url is none"))?; + let sigstore_base_url = self + .sig_store_config_file + .base_url(&image.reference)? + .ok_or_else(|| anyhow!("The sigstore base url is none"))?; - let sigstore = format!("{}/{}", &sigstore_base_url, &sigstore_name); - let sigstore_uri = - url::Url::parse(&sigstore).map_err(|e| anyhow!("Failed to parse sigstore_uri: {:?}", e))?; + let sigstore = format!("{}/{}", &sigstore_base_url, &sigstore_name); + let sigstore_uri = url::Url::parse(&sigstore) + .map_err(|e| anyhow!("Failed to parse sigstore_uri: {:?}", e))?; - let sigs = sigstore::get_sigs_from_specific_sigstore(sigstore_uri).await?; + let sigs = sigstore::get_sigs_from_specific_sigstore(sigstore_uri).await?; - Ok(sigs) + Ok(sigs) + } } diff --git a/src/signature/mechanism/simple/sigstore.rs b/src/signature/mechanism/simple/sigstore.rs index 231e04390..96b153195 100644 --- a/src/signature/mechanism/simple/sigstore.rs +++ b/src/signature/mechanism/simple/sigstore.rs @@ -26,7 +26,7 @@ pub fn format_sigstore_name(image_ref: &Reference, image_digest: image::digest:: // Defines sigstore locations (sigstore base url) for a single namespace. // Please refer to https://github.com/containers/image/blob/main/docs/containers-registries.d.5.md for more details. -#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Eq)] pub struct SigstoreConfig { #[serde(rename = "default-docker")] default_config: Option, @@ -57,36 +57,56 @@ impl SigstoreConfig { let config = serde_yaml::from_str::(&config_yaml_string)?; // The "default-docker" only allowed to be defined in one config file. - if config.default_config.is_some() { - if merged_config.default_config.is_some() { - bail!("Error parsing sigstore config: \"default-docker\" defined repeatedly."); - } - merged_config.default_config = config.default_config; + merged_config.update_self(config)?; + } + + Ok(merged_config) + } + + /// Update current [`SigstoreConfig`] using another [`SigstoreConfig`]. + /// - If current [`SigstoreConfig`] does not have a `default_config` but the input [`SigstoreConfig`] + /// has one, the current [`SigstoreConfig`] will use the input one's `default_config`. + /// - If no duplicated `docker_namespace_config` is found in the input [`SigstoreConfig`], + /// the current [`SigstoreConfig`] will be added all the input one's `docker_namespace_config`. + /// Any error will cause the update fail, and roll back to the original state. + pub fn update_self(&mut self, input: SigstoreConfig) -> Result<()> { + let mut merged_config = self.clone(); + // The "default-docker" only allowed to be defined in one config file. + if input.default_config.is_some() { + if merged_config.default_config.is_some() + && input.default_config != merged_config.default_config + { + bail!("Error parsing sigstore config: \"default-docker\" defined repeatedly but differently."); } + merged_config.default_config = input.default_config; + } - // An image namespace is not allowed appear in two different configuration files. - if let Some(docker_config_map) = config.docker_namespace_config { - for (ns_name, ns_config) in docker_config_map.iter() { - if merged_config.contains_namespace(ns_name) { + // Different SigstoreConfigEntry of same namespace is not allowed to appear in two different configuration files. + if let Some(docker_config_map) = input.docker_namespace_config { + for (ns_name, ns_config) in docker_config_map.iter() { + if let Some(namespace) = merged_config.get_namespace(ns_name) { + if namespace != ns_config { bail!( "Error parsing sigstore config: {} defined repeatedly.", &ns_name ); } - - merged_config.insert(ns_name, ns_config); + continue; } + + merged_config.insert(ns_name, ns_config); } } - Ok(merged_config) + *self = merged_config; + Ok(()) } - fn contains_namespace(&self, ns: &str) -> bool { + fn get_namespace(&self, ns: &str) -> Option<&SigstoreConfigEntry> { if let Some(docker) = &self.docker_namespace_config { - docker.get(ns).is_some() + docker.get(ns) } else { - false + None } } @@ -125,7 +145,7 @@ impl SigstoreConfig { } } -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Eq)] struct SigstoreConfigEntry { sigstore: String, } @@ -223,7 +243,7 @@ mod tests { }, ]; - let test_sigstore_config_dir = "./test_data/signature/sigstore_config"; + let test_sigstore_config_dir = "./test_data/signature/sigstore_config/get_base_url"; let sigstore_config = SigstoreConfig::new_from_configs(test_sigstore_config_dir) .await .unwrap(); @@ -247,20 +267,21 @@ mod tests { merged_res_path: &'a str, } - let tests_unexpect = &[ - "./test_data/signature/sigstore_config/test_case_1", - "./test_data/signature/sigstore_config/test_case_2", + let tests_expect = &[ + TestData { + sigstore_config_path: "./test_data/signature/sigstore_config/test_case_3", + merged_res_path: "./test_data/signature/sigstore_config/merged_result/res3.yaml", + }, + TestData { + sigstore_config_path: "./test_data/signature/sigstore_config/test_case_2", + merged_res_path: "./test_data/signature/sigstore_config/merged_result/res2.yaml", + }, + TestData { + sigstore_config_path: "./test_data/signature/sigstore_config/test_case_1", + merged_res_path: "./test_data/signature/sigstore_config/merged_result/res1.yaml", + }, ]; - let tests_expect = &[TestData { - sigstore_config_path: "./test_data/signature/sigstore_config/test_case_3", - merged_res_path: "./test_data/signature/sigstore_config/res.yaml", - }]; - - for case in tests_unexpect.iter() { - assert!(SigstoreConfig::new_from_configs(case).await.is_err()); - } - for case in tests_expect.iter() { let merged_string = fs::read_to_string(case.merged_res_path).unwrap(); let merged_config = serde_yaml::from_str::(&merged_string).unwrap(); @@ -268,7 +289,7 @@ mod tests { merged_config, SigstoreConfig::new_from_configs(case.sigstore_config_path) .await - .unwrap() + .expect("new sigstore config from filesystem") ); } } diff --git a/src/signature/mechanism/simple/verify.rs b/src/signature/mechanism/simple/verify.rs index 26b064468..e3e95cd21 100644 --- a/src/signature/mechanism/simple/verify.rs +++ b/src/signature/mechanism/simple/verify.rs @@ -51,9 +51,9 @@ impl SigKeyIDs { // Verifies the input signature, and verifies its principal components match expected // values, both as specified by rules, and returns the signature payload. -pub fn verify_sig_and_extract_payload(pubkey_ring: Vec, sig: Vec) -> Result { +pub fn verify_sig_and_extract_payload(pubkey_ring: &[u8], sig: Vec) -> Result { // Parse the gpg pubkey ring. - let keyring_packet = PacketPile::from_bytes(&pubkey_ring)?; + let keyring_packet = PacketPile::from_bytes(pubkey_ring)?; let keyring_iter = keyring_packet.descendants(); // Parse the signature cliam file into sequoia-opengpg PacketPile format. let mut sig_packet = PacketPile::from_bytes(&sig)?; @@ -253,7 +253,7 @@ mod tests { ::std::fs::read("./test_data/signature/signatures/signature-1").unwrap(); let sig_payload_verified = - verify_sig_and_extract_payload(keyring_bytes_case_1, sig_bytes_case_1).unwrap(); + verify_sig_and_extract_payload(&keyring_bytes_case_1, sig_bytes_case_1).unwrap(); let sig_payload_verified = serde_json::to_value(&sig_payload_verified).unwrap(); diff --git a/src/signature/mod.rs b/src/signature/mod.rs index 9e31b551f..3ba5641dd 100644 --- a/src/signature/mod.rs +++ b/src/signature/mod.rs @@ -8,124 +8,41 @@ pub mod mechanism; pub mod payload; pub mod policy; -#[cfg(feature = "getresource")] -pub use getresource::allows_image; - -#[cfg(not(feature = "getresource"))] -pub use no_getresource::allows_image; - -#[cfg(feature = "getresource")] -pub mod getresource { - use crate::config::Paths; - use crate::secure_channel::SecureChannel; - use crate::signature::policy::Policy; - - use super::image::Image; - - use std::convert::TryFrom; - use std::sync::Arc; - - use anyhow::Result; - use oci_distribution::secrets::RegistryAuth; - use tokio::sync::Mutex; - - /// `allows_image` will check all the `PolicyRequirements` suitable for - /// the given image. The `PolicyRequirements` is defined in - /// [`policy_path`] and may include signature verification. - #[cfg(all(feature = "getresource", feature = "signature"))] - pub async fn allows_image( - image_reference: &str, - image_digest: &str, - secure_channel: Arc>, - auth: &RegistryAuth, - paths: &Paths, - ) -> Result<()> { - // if Policy config file does not exist, get if from KBS. - if !std::path::Path::new(&paths.policy_path).exists() { - secure_channel - .lock() - .await - .get_resource( - "Policy", - std::collections::HashMap::new(), - &paths.policy_path, - ) - .await?; - } - - let reference = oci_distribution::Reference::try_from(image_reference)?; - let mut image = Image::default_with_reference(reference); - image.set_manifest_digest(image_digest)?; - - // Read the set of signature schemes that need to be verified - // of the image from the policy configuration. - let mut policy = Policy::from_file(&paths.policy_path).await?; - let schemes = policy.signature_schemes(&image); - - // Get the necessary resources from KBS if needed. - for scheme in schemes { - scheme.init(paths).await?; - let resource_manifest = scheme.resource_manifest(); - for (resource_name, path) in resource_manifest { - secure_channel - .lock() - .await - .get_resource(resource_name, std::collections::HashMap::new(), path) - .await?; - } - } - - policy - .is_image_allowed(image, auth) - .await - .map_err(|e| anyhow::anyhow!("Validate image failed: {:?}", e)) +use crate::{config::Paths, signature::policy::Policy}; +use std::convert::TryFrom; + +use anyhow::Result; +use oci_distribution::secrets::RegistryAuth; + +/// `allows_image` will check all the `PolicyRequirements` suitable for +/// the given image. The `PolicyRequirements` is defined in +/// [`policy_path`] and may include signature verification. +#[cfg(feature = "signature")] +pub async fn allows_image( + image_reference: &str, + image_digest: &str, + auth: &RegistryAuth, + file_paths: &Paths, +) -> Result<()> { + use crate::{resource, signature::image::Image}; + + let reference = oci_distribution::Reference::try_from(image_reference)?; + let mut image = Image::default_with_reference(reference); + image.set_manifest_digest(image_digest)?; + + // Read the set of signature schemes that need to be verified + // of the image from the policy configuration. + let policy_json_string = resource::get_resource(&file_paths.policy_path).await?; + let mut policy = serde_json::from_slice::(&policy_json_string)?; + let schemes = policy.signature_schemes(&image); + + // Get the necessary resources from KBS if needed. + for scheme in schemes { + scheme.init(file_paths).await?; } -} - -#[cfg(not(feature = "getresource"))] -pub mod no_getresource { - use std::convert::TryFrom; - - use anyhow::Result; - use log::warn; - use oci_distribution::secrets::RegistryAuth; - - use crate::{ - config::Paths, - signature::{image::Image, policy::Policy}, - }; - - pub async fn allows_image( - image_reference: &str, - image_digest: &str, - auth: &RegistryAuth, - paths: &Paths, - ) -> Result<()> { - // if Policy config file does not exist, get if from KBS. - let policy_path = &paths.policy_path; - if !std::path::Path::new(policy_path).exists() { - warn!("Non {policy_path} found, pass validation."); - return Ok(()); - } - - let reference = oci_distribution::Reference::try_from(image_reference)?; - let mut image = Image::default_with_reference(reference); - image.set_manifest_digest(image_digest)?; - - // Read the set of signature schemes that need to be verified - // of the image from the policy configuration. - let mut policy = Policy::from_file(policy_path).await?; - let schemes = policy.signature_schemes(&image); - - // Get the necessary resources from KBS if needed. - for scheme in schemes { - scheme.init(paths).await?; - } - - policy - .is_image_allowed(image, auth) - .await - .map_err(|e| anyhow::anyhow!("Validate image failed: {:?}", e)) - } + policy + .is_image_allowed(image, auth) + .await + .map_err(|e| anyhow::anyhow!("Validate image failed: {:?}", e)) } diff --git a/src/signature/policy/mod.rs b/src/signature/policy/mod.rs index c23f3e3b6..bfdf994b3 100644 --- a/src/signature/policy/mod.rs +++ b/src/signature/policy/mod.rs @@ -3,13 +3,12 @@ // SPDX-License-Identifier: Apache-2.0 // -use anyhow::{anyhow, bail, Result}; +use anyhow::{bail, Result}; use oci_distribution::secrets::RegistryAuth; use serde::Deserialize; use std::collections::HashMap; use std::vec::Vec; use strum_macros::{Display, EnumString}; -use tokio::fs; use self::policy_requirement::PolicyReqType; @@ -44,16 +43,6 @@ pub struct Policy { pub type PolicyTransportScopes = HashMap>; impl Policy { - // Parse the JSON file of policy (policy.json). - pub async fn from_file(file_path: &str) -> Result { - let policy_json_string = fs::read_to_string(file_path) - .await - .map_err(|e| anyhow!("Read policy.json file failed: {:?}", e))?; - - let policy = serde_json::from_str::(&policy_json_string)?; - Ok(policy) - } - // Returns Ok(()) if the requirement allows running an image. // WARNING: This validates signatures and the manifest, but does not download or validate the // layers. Users must validate that the layers match their expected digests. diff --git a/src/signature/policy/policy_requirement.rs b/src/signature/policy/policy_requirement.rs index 15f6ea58e..0f3ae0cbe 100644 --- a/src/signature/policy/policy_requirement.rs +++ b/src/signature/policy/policy_requirement.rs @@ -139,27 +139,21 @@ mod tests { key_path: Some("/keys/public-gpg-keyring".into()), key_data: None, signed_identity: None, - sig_store_config_dir: "".into(), - default_sig_store_config_file: "".into(), - gpg_key_ring: "".into(), + ..Default::default() }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), key_path: None, key_data: Some("bm9uc2Vuc2U=".into()), signed_identity: None, - sig_store_config_dir: "".into(), - default_sig_store_config_file: "".into(), - gpg_key_ring: "".into(), + ..Default::default() }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), key_path: Some("/keys/public-gpg-keyring".into()), key_data: None, signed_identity: Some(PolicyReqMatchType::MatchExact), - sig_store_config_dir: "".into(), - default_sig_store_config_file: "".into(), - gpg_key_ring: "".into(), + ..Default::default() }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), @@ -168,9 +162,7 @@ mod tests { signed_identity: Some(PolicyReqMatchType::ExactReference { docker_reference: "docker.io/example/busybox:latest".into(), }), - sig_store_config_dir: "".into(), - default_sig_store_config_file: "".into(), - gpg_key_ring: "".into(), + ..Default::default() }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), @@ -180,9 +172,7 @@ mod tests { prefix: "example".into(), signed_prefix: "example".into(), }), - sig_store_config_dir: "".into(), - default_sig_store_config_file: "".into(), - gpg_key_ring: "".into(), + ..Default::default() }), ]; diff --git a/test_data/offline-fs-kbc/aa-offline_fs_kbc-keys.json b/test_data/offline-fs-kbc/aa-offline_fs_kbc-keys.json index 0350f1dc6..dfb7ae73a 100644 --- a/test_data/offline-fs-kbc/aa-offline_fs_kbc-keys.json +++ b/test_data/offline-fs-kbc/aa-offline_fs_kbc-keys.json @@ -1,12 +1,12 @@ { - "key_id1": "cGFzc3BocmFzZXdoaWNobmVlZHN0b2JlMzJieXRlcyE=", - "key_id2": "qyecaCQh/+ChjnO7poTn08w5WYFqz4piBPh1rVMmrEo=", - "key_id3": "sXnCqg66/XdMb6Lo+XkABR9v8b3ImJm9BwtxhOMVwmg=", - "key_id4": "nXHQzVVvpVm9OrpRGwOIJ53cyAv2IRphLjJkS0dusT0=", - "key_id5": "gsyZwJh9BI/0qrCvfGxqJudcgHPRnymCgcych2lNzyU=", - "key_id6": "+2Cr3LQ8sq3PQpXIRX7yUNIP4q1X7HczSkB/FHZWGwQ=", - "key_id7": "Y8jJ64qyDOV8+M2ZEyAAk58P6rDdnDGg3WNgn2ELdVU=", - "key_id8": "EfZNaUaszIjCgT6YtSLiaLDMdPBMtZNQXTrBf3DmiP0=", - "key_id9": "illVfAwybTUAazagcOy90wLzrMPQVm44fZWxyeigjxo=", - "key_id10": "1g3KtvGfyIjq+HZmsKYJ1tMzuB8f1RjS6H0ieNBHLV0=" + "default/key/1": "cGFzc3BocmFzZXdoaWNobmVlZHN0b2JlMzJieXRlcyE=", + "default/key/2": "qyecaCQh/+ChjnO7poTn08w5WYFqz4piBPh1rVMmrEo=", + "default/key/3": "sXnCqg66/XdMb6Lo+XkABR9v8b3ImJm9BwtxhOMVwmg=", + "default/key/4": "nXHQzVVvpVm9OrpRGwOIJ53cyAv2IRphLjJkS0dusT0=", + "default/key/5": "gsyZwJh9BI/0qrCvfGxqJudcgHPRnymCgcych2lNzyU=", + "default/key/6": "+2Cr3LQ8sq3PQpXIRX7yUNIP4q1X7HczSkB/FHZWGwQ=", + "default/key/7": "Y8jJ64qyDOV8+M2ZEyAAk58P6rDdnDGg3WNgn2ELdVU=", + "default/key/8": "EfZNaUaszIjCgT6YtSLiaLDMdPBMtZNQXTrBf3DmiP0=", + "default/key/9": "illVfAwybTUAazagcOy90wLzrMPQVm44fZWxyeigjxo=", + "default/key/10": "1g3KtvGfyIjq+HZmsKYJ1tMzuB8f1RjS6H0ieNBHLV0=" } \ No newline at end of file diff --git a/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-no-cosign.json b/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-no-cosign.json deleted file mode 100644 index 968da0607..000000000 --- a/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-no-cosign.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Policy": "ewogICAgImRlZmF1bHQiOiBbCiAgICAgICAgewogICAgICAgICAgICAidHlwZSI6ICJpbnNlY3VyZUFjY2VwdEFueXRoaW5nIgogICAgICAgIH0KICAgIF0sCiAgICAidHJhbnNwb3J0cyI6IHsKICAgICAgICAiZG9ja2VyIjogewogICAgICAgICAgICAicXVheS5pby9rYXRhLWNvbnRhaW5lcnMvY29uZmlkZW50aWFsLWNvbnRhaW5lcnMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAic2lnbmVkQnkiLAogICAgICAgICAgICAgICAgICAgICJrZXlUeXBlIjogIkdQR0tleXMiLAogICAgICAgICAgICAgICAgICAgICJrZXlQYXRoIjogIi9ydW4vaW1hZ2Utc2VjdXJpdHkvc2ltcGxlX3NpZ25pbmcvcHVia2V5LmdwZyIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgXQogICAgICAgIH0KICAgIH0KfQo=", - "Sigstore Config": "ZG9ja2VyOgogICAgcXVheS5pby9rYXRhLWNvbnRhaW5lcnMvY29uZmlkZW50aWFsLWNvbnRhaW5lcnM6CiAgICAgICAgc2lnc3RvcmU6IGZpbGU6Ly8vZXRjL2NvbnRhaW5lcnMvcXVheV92ZXJpZmljYXRpb24vc2lnbmF0dXJlcwogICAgICAgIHNpZ3N0b3JlLXN0YWdpbmc6IGZpbGU6Ly8vZXRjL2NvbnRhaW5lcnMvcXVheV92ZXJpZmljYXRpb24vc2lnbmF0dXJlcw==", - "GPG Keyring": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCgptUUlOQkdGTVZFZ0JFQUN6ZC9ISno2bnE4R0FqRm9XdDIwUGhBeTRScDhxNHFlRkUzSkorbHdoUHprSmRiTDNaClFKMzFURUNyYktVeW8zTElRMzFCNzVBWXczdm5FSVVPY3V0U0UxaThvNTU3SW94eGxHNFN3dGtSVmRVUGVFN2UKdElOMm1aKzJHd25nQW1KRUgxNWtNQUZzVVFhNG4rWE9WUU9aSTNRWWVsWWpMd0thbXFBa3dFdjAzSmpHaTIrbQo0a0ZITzBmMy9lc0pmZXhVd3hLMHdQazJ4emlvZ2FpTzN6NDViTkoxMDZwSC95NGhRMHBWbWZJSHpPVjZwRHN2ClVHcTFxdnZlL2dDRXFZZWYvcUgyNzJoRkdNTE1qRy8yOStwVmZ1bEJ2YnpiUUhNUHlIaTFBdTVwemJWVUhxOUEKOURoWXhmWllpN2MreXU5Y1h0cngzQmlXSG52NzlBRUtWZDhCdkVucE02dGNIOWMvVFJlakd6VjF0cThva05wMwpXaXp6T0ZzVXBpaXVYVVo5ZlVlQ0s5YnVEaXdsdDF2ZGQ2OG5RZ3o2YkdIOEZqbVd2UXU4eTNVZEZRSTUwQkNVCmVEeFZEcHIzRXhjNER6MWxnU0pNV0wya2NJRy8wVllGU2hkRXUxL2lnNmdLUlpGcm1XN2hnSU51V1ZwWUNoZGkKK0I3Rkg1UDhGUlBiN0YrZFdyY0o3M3A1WXJLMzhHbnpadTNtdmZSUnk5Q0FpU1NFNFpEd0JuMjMzSCtlMFFzWAptT2lIcW1LSVZTbnhVa1hoTktXWm9LUDVQRlBHWE9YSEFNaWRnWC8wT0UxOEc2WmREMEYvRVNuYVdUL2lwNzNNCk1EYU5tVENlL2JZdW9TZy9oVUdCMEtENUx2aFZaT01haTh1MkYwQnJFYWdPRnQ3SkZjbUVwd2pXWndBUkFRQUIKdEVsVGRHVjJaVzRnU0c5eWMyMWhiaUFvUjFCSElHdGxlU0JtYjNJZ2MybG5ibWx1WnlCcllYUmhJSFJsYzNRZwphVzFoWjJWektTQThjM1JsZG1WdVFIVnJMbWxpYlM1amIyMCtpUUpZQkJNQkNBQkNGaUVFWjdKS3JNUlpaNTRDCmc5ZnVXUGJ0Qis2bXRDa0ZBbUZNVkVnQ0d3TUZDUUhoTTRBRkN3a0lCd0lESWdJQkJoVUtDUWdMQWdRV0FnTUIKQWg0SEFoZUFBQW9KRUZqMjdRZnVwclFwc3ZBUC8zTit5RGRlRkRMaVdSS21YbEhzbWRuT3dlYVdxQjdzUWJ0SQpJTFh6RVFCY1pIWjFRNUxna0o2bzlHUlJlK0pPVmFsQUQ5QXdPQjg4Z0hNVVptR2hmQU05dnY3R3RWWGdpQkNmCi9mNDE0TTFueS9xMUgwZG1wRnF4b3FaYzlXNlhaU1pFVC8yNVFPUlMzYkxIK0dFdnQ4enZaUkFLVU9WRUhPZTQKbHRocmNuY21uaFd4ZWc0ZFJGWEZRczJZSW41VzZiOTd4SzN4emF0bDlyTVgwd2s4L2xweDlHQ0tLalZ3OVpQcwpUZ25kcmlMTnUzaGJOeWFXaEhlTHFUT1hEOUU0WUNjM3FMc0MvZW5Hclh6Si91bWdpaHUvRy9iNWFsZWZ6U09xCnh0MHI2ejdSbk85OXJVdEtDYW0rNUVEa0t6VXZoamdSM2oyTGtHWkMxZnFBTnQ2TEtPK0MwT3FtMEpUMm1UZGEKdGEveDdCdGozNktJYjN1TlNSdDJiRHJGWXhPajZzRnlQVlRVbHpOZ2l0bkszVHFJeG5teWlHZGhPVUcyc1p5OAowSTFaNHZaT0JGdzIzWE9qYzRUVGRWU29BbUxSZkhOeWZtYXlHbS9ja2xlTjV2T2xiVzlPOXREa0M0alo2WkZNCjFxZzEyUkxvS1dxRXRodmlzOVhzV0xieEFBaG0xbkZKV0VpTlhzdW1NUDc0U1cwLy9qYmRFT0xObzBXRG5TTmIKZ3U2a2hVYXJIR0dpUEJzeFc4cURGdXNIWFplMEpDSVFRUTBDZVh3T1owaXFINC9tQ0lKQnlId2dEdExnbnNUTQo2a2hnU2VhMXk1a3RRQnZSdU1QODg5ZWJQSEoyNjFqeUl5OXV5K25oaUt5cG9PK3lqMWYvUm5qNWtLS3Y3Mm5LCjV1RVNwSkJUCj1CN3ZRCi0tLS0tRU5EIFBHUCBQVUJMSUMgS0VZIEJMT0NLLS0tLS0K", - "Cosign Key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMWdIR2JmazFBcU93ZUxFTThIZlQwYm1mUUUzYgo5ZmNwL0xVNzVGTWZ4VlpYbU5WdFVwcnNITTF0aHV1aUJLT29mdjhLVjdUckZsNHA4TkpDaVhVa2hBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==", - "Credential": "ewogICAgImF1dGhzIjogewogICAgICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0sCiAgICAgICAgInF1YXkuaW8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0KICAgIH0KfQ==" - } \ No newline at end of file diff --git a/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources.json b/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources.json index 5d0dac2cf..1a8be5705 100644 --- a/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources.json +++ b/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources.json @@ -1,7 +1,7 @@ { - "Policy": "ewogICAgImRlZmF1bHQiOiBbCiAgICAgICAgewogICAgICAgICAgICAidHlwZSI6ICJpbnNlY3VyZUFjY2VwdEFueXRoaW5nIgogICAgICAgIH0KICAgIF0sCiAgICAidHJhbnNwb3J0cyI6IHsKICAgICAgICAiZG9ja2VyIjogewogICAgICAgICAgICAicXVheS5pby9rYXRhLWNvbnRhaW5lcnMvY29uZmlkZW50aWFsLWNvbnRhaW5lcnMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAic2lnbmVkQnkiLAogICAgICAgICAgICAgICAgICAgICJrZXlUeXBlIjogIkdQR0tleXMiLAogICAgICAgICAgICAgICAgICAgICJrZXlQYXRoIjogIi9ydW4vaW1hZ2Utc2VjdXJpdHkvc2ltcGxlX3NpZ25pbmcvcHVia2V5LmdwZyIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgInF1YXkuaW8va2F0YS1jb250YWluZXJzL2NvbmZpZGVudGlhbC1jb250YWluZXJzOmNvc2lnbi1zaWduZWQiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAic2lnc3RvcmVTaWduZWQiLAogICAgICAgICAgICAgICAgICAgICJrZXlQYXRoIjogIi9ydW4vaW1hZ2Utc2VjdXJpdHkvY29zaWduL2Nvc2lnbi5wdWIiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJxdWF5LmlvL2thdGEtY29udGFpbmVycy9jb25maWRlbnRpYWwtY29udGFpbmVyczpjb3NpZ24tc2lnbmVkLWtleTIiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAic2lnc3RvcmVTaWduZWQiLAogICAgICAgICAgICAgICAgICAgICJrZXlQYXRoIjogIi9ydW4vaW1hZ2Utc2VjdXJpdHkvY29zaWduL2Nvc2lnbi5wdWIiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0KICAgICAgICB9CiAgICB9Cn0=", - "Sigstore Config": "ZG9ja2VyOgogICAgcXVheS5pby9rYXRhLWNvbnRhaW5lcnMvY29uZmlkZW50aWFsLWNvbnRhaW5lcnM6CiAgICAgICAgc2lnc3RvcmU6IGZpbGU6Ly8vZXRjL2NvbnRhaW5lcnMvcXVheV92ZXJpZmljYXRpb24vc2lnbmF0dXJlcwogICAgICAgIHNpZ3N0b3JlLXN0YWdpbmc6IGZpbGU6Ly8vZXRjL2NvbnRhaW5lcnMvcXVheV92ZXJpZmljYXRpb24vc2lnbmF0dXJlcw==", - "GPG Keyring": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCgptUUlOQkdGTVZFZ0JFQUN6ZC9ISno2bnE4R0FqRm9XdDIwUGhBeTRScDhxNHFlRkUzSkorbHdoUHprSmRiTDNaClFKMzFURUNyYktVeW8zTElRMzFCNzVBWXczdm5FSVVPY3V0U0UxaThvNTU3SW94eGxHNFN3dGtSVmRVUGVFN2UKdElOMm1aKzJHd25nQW1KRUgxNWtNQUZzVVFhNG4rWE9WUU9aSTNRWWVsWWpMd0thbXFBa3dFdjAzSmpHaTIrbQo0a0ZITzBmMy9lc0pmZXhVd3hLMHdQazJ4emlvZ2FpTzN6NDViTkoxMDZwSC95NGhRMHBWbWZJSHpPVjZwRHN2ClVHcTFxdnZlL2dDRXFZZWYvcUgyNzJoRkdNTE1qRy8yOStwVmZ1bEJ2YnpiUUhNUHlIaTFBdTVwemJWVUhxOUEKOURoWXhmWllpN2MreXU5Y1h0cngzQmlXSG52NzlBRUtWZDhCdkVucE02dGNIOWMvVFJlakd6VjF0cThva05wMwpXaXp6T0ZzVXBpaXVYVVo5ZlVlQ0s5YnVEaXdsdDF2ZGQ2OG5RZ3o2YkdIOEZqbVd2UXU4eTNVZEZRSTUwQkNVCmVEeFZEcHIzRXhjNER6MWxnU0pNV0wya2NJRy8wVllGU2hkRXUxL2lnNmdLUlpGcm1XN2hnSU51V1ZwWUNoZGkKK0I3Rkg1UDhGUlBiN0YrZFdyY0o3M3A1WXJLMzhHbnpadTNtdmZSUnk5Q0FpU1NFNFpEd0JuMjMzSCtlMFFzWAptT2lIcW1LSVZTbnhVa1hoTktXWm9LUDVQRlBHWE9YSEFNaWRnWC8wT0UxOEc2WmREMEYvRVNuYVdUL2lwNzNNCk1EYU5tVENlL2JZdW9TZy9oVUdCMEtENUx2aFZaT01haTh1MkYwQnJFYWdPRnQ3SkZjbUVwd2pXWndBUkFRQUIKdEVsVGRHVjJaVzRnU0c5eWMyMWhiaUFvUjFCSElHdGxlU0JtYjNJZ2MybG5ibWx1WnlCcllYUmhJSFJsYzNRZwphVzFoWjJWektTQThjM1JsZG1WdVFIVnJMbWxpYlM1amIyMCtpUUpZQkJNQkNBQkNGaUVFWjdKS3JNUlpaNTRDCmc5ZnVXUGJ0Qis2bXRDa0ZBbUZNVkVnQ0d3TUZDUUhoTTRBRkN3a0lCd0lESWdJQkJoVUtDUWdMQWdRV0FnTUIKQWg0SEFoZUFBQW9KRUZqMjdRZnVwclFwc3ZBUC8zTit5RGRlRkRMaVdSS21YbEhzbWRuT3dlYVdxQjdzUWJ0SQpJTFh6RVFCY1pIWjFRNUxna0o2bzlHUlJlK0pPVmFsQUQ5QXdPQjg4Z0hNVVptR2hmQU05dnY3R3RWWGdpQkNmCi9mNDE0TTFueS9xMUgwZG1wRnF4b3FaYzlXNlhaU1pFVC8yNVFPUlMzYkxIK0dFdnQ4enZaUkFLVU9WRUhPZTQKbHRocmNuY21uaFd4ZWc0ZFJGWEZRczJZSW41VzZiOTd4SzN4emF0bDlyTVgwd2s4L2xweDlHQ0tLalZ3OVpQcwpUZ25kcmlMTnUzaGJOeWFXaEhlTHFUT1hEOUU0WUNjM3FMc0MvZW5Hclh6Si91bWdpaHUvRy9iNWFsZWZ6U09xCnh0MHI2ejdSbk85OXJVdEtDYW0rNUVEa0t6VXZoamdSM2oyTGtHWkMxZnFBTnQ2TEtPK0MwT3FtMEpUMm1UZGEKdGEveDdCdGozNktJYjN1TlNSdDJiRHJGWXhPajZzRnlQVlRVbHpOZ2l0bkszVHFJeG5teWlHZGhPVUcyc1p5OAowSTFaNHZaT0JGdzIzWE9qYzRUVGRWU29BbUxSZkhOeWZtYXlHbS9ja2xlTjV2T2xiVzlPOXREa0M0alo2WkZNCjFxZzEyUkxvS1dxRXRodmlzOVhzV0xieEFBaG0xbkZKV0VpTlhzdW1NUDc0U1cwLy9qYmRFT0xObzBXRG5TTmIKZ3U2a2hVYXJIR0dpUEJzeFc4cURGdXNIWFplMEpDSVFRUTBDZVh3T1owaXFINC9tQ0lKQnlId2dEdExnbnNUTQo2a2hnU2VhMXk1a3RRQnZSdU1QODg5ZWJQSEoyNjFqeUl5OXV5K25oaUt5cG9PK3lqMWYvUm5qNWtLS3Y3Mm5LCjV1RVNwSkJUCj1CN3ZRCi0tLS0tRU5EIFBHUCBQVUJMSUMgS0VZIEJMT0NLLS0tLS0K", - "Cosign Key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMWdIR2JmazFBcU93ZUxFTThIZlQwYm1mUUUzYgo5ZmNwL0xVNzVGTWZ4VlpYbU5WdFVwcnNITTF0aHV1aUJLT29mdjhLVjdUckZsNHA4TkpDaVhVa2hBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==", - "Credential": "ewogICAgImF1dGhzIjogewogICAgICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0sCiAgICAgICAgInF1YXkuaW8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0KICAgIH0KfQ==" + "default/security-policy/test": "ewogICAgImRlZmF1bHQiOiBbCiAgICAgICAgewogICAgICAgICAgICAidHlwZSI6ICJpbnNlY3VyZUFjY2VwdEFueXRoaW5nIgogICAgICAgIH0KICAgIF0sCiAgICAidHJhbnNwb3J0cyI6IHsKICAgICAgICAiZG9ja2VyIjogewogICAgICAgICAgICAicXVheS5pby9rYXRhLWNvbnRhaW5lcnMvY29uZmlkZW50aWFsLWNvbnRhaW5lcnMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAic2lnbmVkQnkiLAogICAgICAgICAgICAgICAgICAgICJrZXlUeXBlIjogIkdQR0tleXMiLAogICAgICAgICAgICAgICAgICAgICJrZXlQYXRoIjogImticzovLy9kZWZhdWx0L2dwZy1wdWJsaWMta2V5L3Rlc3QiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJxdWF5LmlvL2thdGEtY29udGFpbmVycy9jb25maWRlbnRpYWwtY29udGFpbmVyczpjb3NpZ24tc2lnbmVkIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJ0eXBlIjogInNpZ3N0b3JlU2lnbmVkIiwKICAgICAgICAgICAgICAgICAgICAia2V5UGF0aCI6ICJrYnM6Ly8vZGVmYXVsdC9jb3NpZ24tcHVibGljLWtleS90ZXN0IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICAgICAicXVheS5pby9rYXRhLWNvbnRhaW5lcnMvY29uZmlkZW50aWFsLWNvbnRhaW5lcnM6Y29zaWduLXNpZ25lZC1rZXkyIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJ0eXBlIjogInNpZ3N0b3JlU2lnbmVkIiwKICAgICAgICAgICAgICAgICAgICAia2V5UGF0aCI6ICJrYnM6Ly8vZGVmYXVsdC9jb3NpZ24tcHVibGljLWtleS90ZXN0IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICBdCiAgICAgICAgfQogICAgfQp9", + "default/sigstore-config/test": "ZG9ja2VyOgogICAgcXVheS5pby9rYXRhLWNvbnRhaW5lcnMvY29uZmlkZW50aWFsLWNvbnRhaW5lcnM6CiAgICAgICAgc2lnc3RvcmU6IGZpbGU6Ly8vZXRjL2NvbnRhaW5lcnMvcXVheV92ZXJpZmljYXRpb24vc2lnbmF0dXJlcwogICAgICAgIHNpZ3N0b3JlLXN0YWdpbmc6IGZpbGU6Ly8vZXRjL2NvbnRhaW5lcnMvcXVheV92ZXJpZmljYXRpb24vc2lnbmF0dXJlcw==", + "default/gpg-public-key/test": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCgptUUlOQkdGTVZFZ0JFQUN6ZC9ISno2bnE4R0FqRm9XdDIwUGhBeTRScDhxNHFlRkUzSkorbHdoUHprSmRiTDNaClFKMzFURUNyYktVeW8zTElRMzFCNzVBWXczdm5FSVVPY3V0U0UxaThvNTU3SW94eGxHNFN3dGtSVmRVUGVFN2UKdElOMm1aKzJHd25nQW1KRUgxNWtNQUZzVVFhNG4rWE9WUU9aSTNRWWVsWWpMd0thbXFBa3dFdjAzSmpHaTIrbQo0a0ZITzBmMy9lc0pmZXhVd3hLMHdQazJ4emlvZ2FpTzN6NDViTkoxMDZwSC95NGhRMHBWbWZJSHpPVjZwRHN2ClVHcTFxdnZlL2dDRXFZZWYvcUgyNzJoRkdNTE1qRy8yOStwVmZ1bEJ2YnpiUUhNUHlIaTFBdTVwemJWVUhxOUEKOURoWXhmWllpN2MreXU5Y1h0cngzQmlXSG52NzlBRUtWZDhCdkVucE02dGNIOWMvVFJlakd6VjF0cThva05wMwpXaXp6T0ZzVXBpaXVYVVo5ZlVlQ0s5YnVEaXdsdDF2ZGQ2OG5RZ3o2YkdIOEZqbVd2UXU4eTNVZEZRSTUwQkNVCmVEeFZEcHIzRXhjNER6MWxnU0pNV0wya2NJRy8wVllGU2hkRXUxL2lnNmdLUlpGcm1XN2hnSU51V1ZwWUNoZGkKK0I3Rkg1UDhGUlBiN0YrZFdyY0o3M3A1WXJLMzhHbnpadTNtdmZSUnk5Q0FpU1NFNFpEd0JuMjMzSCtlMFFzWAptT2lIcW1LSVZTbnhVa1hoTktXWm9LUDVQRlBHWE9YSEFNaWRnWC8wT0UxOEc2WmREMEYvRVNuYVdUL2lwNzNNCk1EYU5tVENlL2JZdW9TZy9oVUdCMEtENUx2aFZaT01haTh1MkYwQnJFYWdPRnQ3SkZjbUVwd2pXWndBUkFRQUIKdEVsVGRHVjJaVzRnU0c5eWMyMWhiaUFvUjFCSElHdGxlU0JtYjNJZ2MybG5ibWx1WnlCcllYUmhJSFJsYzNRZwphVzFoWjJWektTQThjM1JsZG1WdVFIVnJMbWxpYlM1amIyMCtpUUpZQkJNQkNBQkNGaUVFWjdKS3JNUlpaNTRDCmc5ZnVXUGJ0Qis2bXRDa0ZBbUZNVkVnQ0d3TUZDUUhoTTRBRkN3a0lCd0lESWdJQkJoVUtDUWdMQWdRV0FnTUIKQWg0SEFoZUFBQW9KRUZqMjdRZnVwclFwc3ZBUC8zTit5RGRlRkRMaVdSS21YbEhzbWRuT3dlYVdxQjdzUWJ0SQpJTFh6RVFCY1pIWjFRNUxna0o2bzlHUlJlK0pPVmFsQUQ5QXdPQjg4Z0hNVVptR2hmQU05dnY3R3RWWGdpQkNmCi9mNDE0TTFueS9xMUgwZG1wRnF4b3FaYzlXNlhaU1pFVC8yNVFPUlMzYkxIK0dFdnQ4enZaUkFLVU9WRUhPZTQKbHRocmNuY21uaFd4ZWc0ZFJGWEZRczJZSW41VzZiOTd4SzN4emF0bDlyTVgwd2s4L2xweDlHQ0tLalZ3OVpQcwpUZ25kcmlMTnUzaGJOeWFXaEhlTHFUT1hEOUU0WUNjM3FMc0MvZW5Hclh6Si91bWdpaHUvRy9iNWFsZWZ6U09xCnh0MHI2ejdSbk85OXJVdEtDYW0rNUVEa0t6VXZoamdSM2oyTGtHWkMxZnFBTnQ2TEtPK0MwT3FtMEpUMm1UZGEKdGEveDdCdGozNktJYjN1TlNSdDJiRHJGWXhPajZzRnlQVlRVbHpOZ2l0bkszVHFJeG5teWlHZGhPVUcyc1p5OAowSTFaNHZaT0JGdzIzWE9qYzRUVGRWU29BbUxSZkhOeWZtYXlHbS9ja2xlTjV2T2xiVzlPOXREa0M0alo2WkZNCjFxZzEyUkxvS1dxRXRodmlzOVhzV0xieEFBaG0xbkZKV0VpTlhzdW1NUDc0U1cwLy9qYmRFT0xObzBXRG5TTmIKZ3U2a2hVYXJIR0dpUEJzeFc4cURGdXNIWFplMEpDSVFRUTBDZVh3T1owaXFINC9tQ0lKQnlId2dEdExnbnNUTQo2a2hnU2VhMXk1a3RRQnZSdU1QODg5ZWJQSEoyNjFqeUl5OXV5K25oaUt5cG9PK3lqMWYvUm5qNWtLS3Y3Mm5LCjV1RVNwSkJUCj1CN3ZRCi0tLS0tRU5EIFBHUCBQVUJMSUMgS0VZIEJMT0NLLS0tLS0K", + "default/cosign-public-key/test": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMWdIR2JmazFBcU93ZUxFTThIZlQwYm1mUUUzYgo5ZmNwL0xVNzVGTWZ4VlpYbU5WdFVwcnNITTF0aHV1aUJLT29mdjhLVjdUckZsNHA4TkpDaVhVa2hBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==", + "default/credential/test": "ewogICAgImF1dGhzIjogewogICAgICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0sCiAgICAgICAgInF1YXkuaW8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0KICAgIH0KfQ==" } \ No newline at end of file diff --git a/test_data/signature/sigstore_config/res.yaml b/test_data/signature/sigstore_config/get_base_url/res.yaml similarity index 100% rename from test_data/signature/sigstore_config/res.yaml rename to test_data/signature/sigstore_config/get_base_url/res.yaml diff --git a/test_data/signature/sigstore_config/merged_result/res1.yaml b/test_data/signature/sigstore_config/merged_result/res1.yaml new file mode 100644 index 000000000..ada82bdb9 --- /dev/null +++ b/test_data/signature/sigstore_config/merged_result/res1.yaml @@ -0,0 +1,5 @@ +default-docker: + sigstore: file:///var/lib/containers/sigstore +docker: + example.com: + sigstore: file:///var/lib/containers/sigstore \ No newline at end of file diff --git a/test_data/signature/sigstore_config/merged_result/res2.yaml b/test_data/signature/sigstore_config/merged_result/res2.yaml new file mode 100644 index 000000000..ada82bdb9 --- /dev/null +++ b/test_data/signature/sigstore_config/merged_result/res2.yaml @@ -0,0 +1,5 @@ +default-docker: + sigstore: file:///var/lib/containers/sigstore +docker: + example.com: + sigstore: file:///var/lib/containers/sigstore \ No newline at end of file diff --git a/test_data/signature/sigstore_config/merged_result/res3.yaml b/test_data/signature/sigstore_config/merged_result/res3.yaml new file mode 100644 index 000000000..4e090befc --- /dev/null +++ b/test_data/signature/sigstore_config/merged_result/res3.yaml @@ -0,0 +1,7 @@ +default-docker: + sigstore: file:///default/sigstore +docker: + example1.com: + sigstore: file:///var/lib/containers/sigstore + example2.com: + sigstore: file:///var/lib/containers/sigstore \ No newline at end of file diff --git a/tests/README.md b/tests/README.md index 783db660b..9f7d20a07 100644 --- a/tests/README.md +++ b/tests/README.md @@ -12,7 +12,7 @@ And both of test set will use the following key broker client: Implemented in `image_decryption.rs`. Image decryption will cover `Offline-fs-kbc`: -* `Offline-fs-kbc` uses `docker.io/xynnn007/busybox:encrypted` +* `Offline-fs-kbc` uses `docker.io/xynnn007/busybox:encrypted-uri-key` Each test suite will follow these steps: diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e4c471f11..cd466717d 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -75,7 +75,6 @@ pub async fn start_attestation_agent() -> Result { .output() .await .expect("Failed to build attestation-agent"); - println!("{output:?}"); } } } diff --git a/tests/credential.rs b/tests/credential.rs index 2ae1ca604..bf8682037 100644 --- a/tests/credential.rs +++ b/tests/credential.rs @@ -11,11 +11,11 @@ mod common; #[cfg(feature = "getresource")] #[rstest] -#[case("liudalibj/private-busy-box")] -#[case("quay.io/liudalibj/private-busy-box")] +#[case("liudalibj/private-busy-box", "kbs:///default/credential/test")] +#[case("quay.io/liudalibj/private-busy-box", "kbs:///default/credential/test")] #[tokio::test] #[serial] -async fn test_use_credential(#[case] image_ref: &str) { +async fn test_use_credential(#[case] image_ref: &str, #[case] auth_file_uri: &str) { common::prepare_test().await; // Init AA @@ -42,6 +42,9 @@ async fn test_use_credential(#[case] image_ref: &str) { // enable container auth image_client.config.auth = true; + // set credential file uri + image_client.config.file_paths.auth_file = auth_file_uri.into(); + let bundle_dir = tempfile::tempdir().unwrap(); let res = image_client diff --git a/tests/image_decryption.rs b/tests/image_decryption.rs index d6f408273..890d85936 100644 --- a/tests/image_decryption.rs +++ b/tests/image_decryption.rs @@ -11,10 +11,6 @@ use serial_test::serial; mod common; -/// The image to be decrypted using offline-fs-kbc -const ENCRYPTED_IMAGE_REFERENCE_OFFLINE_FS_KBS: &str = "docker.io/xynnn007/busybox:encrypted"; -const UNENCRYPTED_IMAGE_REFERENCE_OFFLINE_FS_KBS: &str = "docker.io/arronwang/busybox_zstd"; - /// Ocicrypt-rs config for grpc #[cfg(not(feature = "keywrap-ttrpc"))] const OCICRYPT_CONFIG: &str = "test_data/ocicrypt_keyprovider_grpc.conf"; @@ -23,10 +19,12 @@ const OCICRYPT_CONFIG: &str = "test_data/ocicrypt_keyprovider_grpc.conf"; #[cfg(feature = "keywrap-ttrpc")] const OCICRYPT_CONFIG: &str = "test_data/ocicrypt_keyprovider_ttrpc.conf"; -#[cfg(feature = "getresource")] +#[cfg(all(feature = "getresource", feature = "encryption"))] +#[rstest::rstest] +#[case("docker.io/xynnn007/busybox:encrypted-uri-key")] #[tokio::test] #[serial] -async fn test_decrypt_layers() { +async fn test_decrypt_layers(#[case] image: &str) { common::prepare_test().await; // Init AA let mut aa = common::start_attestation_agent() @@ -49,31 +47,16 @@ async fn test_decrypt_layers() { .await .expect("Delete configs failed."); let mut image_client = ImageClient::default(); - let image_name = if cfg!(all(feature = "encryption")) { - ENCRYPTED_IMAGE_REFERENCE_OFFLINE_FS_KBS - } else { - UNENCRYPTED_IMAGE_REFERENCE_OFFLINE_FS_KBS - }; if cfg!(feature = "snapshot-overlayfs") { if let Err(e) = image_client - .pull_image( - image_name, - bundle_dir.path(), - &None, - &Some(common::AA_PARAMETER), - ) + .pull_image(image, bundle_dir.path(), &None, &Some(common::AA_PARAMETER)) .await { panic!("test_decrypt_layers() failed to download image, {}", e); } } else { image_client - .pull_image( - image_name, - bundle_dir.path(), - &None, - &Some(common::AA_PARAMETER), - ) + .pull_image(image, bundle_dir.path(), &None, &Some(common::AA_PARAMETER)) .await .unwrap_err(); } diff --git a/tests/signature_verification.rs b/tests/signature_verification.rs index 76aa9776c..09b06a9d0 100644 --- a/tests/signature_verification.rs +++ b/tests/signature_verification.rs @@ -78,6 +78,10 @@ const _TESTS: [_TestItem; _TEST_ITEMS] = [ }, ]; +const POLICY_URI: &str = "kbs:///default/security-policy/test"; + +const SIGSTORE_CONFIG_URI: &str = "kbs:///default/sigstore-config/test"; + /// image-rs built without support for cosign image signing cannot use a policy that includes a type that /// uses cosign (type: sigstoreSigned), even if the image being pulled is not signed using cosign. /// https://github.com/confidential-containers/attestation-agent/blob/main/src/kbc_modules/sample_kbc/policy.json @@ -108,6 +112,14 @@ async fn signature_verification() { // enable signature verification image_client.config.security_validate = true; + // set the image security policy + image_client.config.file_paths.policy_path = POLICY_URI.into(); + + #[cfg(feature = "signature-simple")] + { + image_client.config.file_paths.sigstore_config = SIGSTORE_CONFIG_URI.into(); + } + let bundle_dir = tempfile::tempdir().unwrap(); let _res = image_client