From a95c2b6aff5c6fa1686c060a3af89df41c0841cb Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sat, 25 Feb 2023 14:51:30 +0800 Subject: [PATCH 1/8] refactor: use ref instead of clone This commit refactors some of the functions of sample signing module. Mainly convert clones into references, which helps to avoid extra memory copies when executing Signed-off-by: Xynnn007 --- src/signature/mechanism/simple/mod.rs | 4 ++-- src/signature/mechanism/simple/verify.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/signature/mechanism/simple/mod.rs b/src/signature/mechanism/simple/mod.rs index ad2e452eb..133b4bc4a 100644 --- a/src/signature/mechanism/simple/mod.rs +++ b/src/signature/mechanism/simple/mod.rs @@ -153,7 +153,7 @@ impl SignScheme for SimpleParameters { 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 +192,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. 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(); From 0ffb15c03591a897aa6dd0a91476772672c8d1c0 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sat, 25 Feb 2023 14:56:51 +0800 Subject: [PATCH 2/8] feat: add support for KBS Resource URI This commit brings in KBS Resource URI support. All the functions related to read a file from given uri has been brought to module `resource`. Now different resources can be fetched using the overall api resource::get_resource() This Api now support two basic protocol: - file:// read file from local filesystem - kbs:// read file from KBS This commit also fixes features of `keywrap-grpc`, `keywrap-ttrpc` and `keywrap-native` to let them to be allowed to exist at the same time. Signed-off-by: Xynnn007 --- Cargo.toml | 8 +- build.rs | 2 +- protos/getresource.proto | 5 +- src/auth/mod.rs | 47 +---- src/config.rs | 26 +-- src/image.rs | 94 +++------- src/lib.rs | 3 +- src/{secure_channel => resource/kbs}/grpc.rs | 10 +- src/resource/kbs/mod.rs | 163 ++++++++++++++++++ .../kbs}/native.rs | 9 +- src/{secure_channel => resource/kbs}/ttrpc.rs | 12 +- .../kbs}/ttrpc_proto/getresource.rs | 71 +++----- .../kbs}/ttrpc_proto/getresource_ttrpc.rs | 0 .../kbs}/ttrpc_proto/mod.rs | 0 src/resource/mod.rs | 78 +++++++++ src/secure_channel/mod.rs | 147 ---------------- src/signature/mechanism/cosign/mod.rs | 51 +----- src/signature/mechanism/mod.rs | 21 +-- src/signature/mechanism/simple/mod.rs | 62 +------ src/signature/mod.rs | 81 +-------- src/signature/policy/mod.rs | 13 +- src/signature/policy/policy_requirement.rs | 15 -- 22 files changed, 331 insertions(+), 587 deletions(-) rename src/{secure_channel => resource/kbs}/grpc.rs (82%) create mode 100644 src/resource/kbs/mod.rs rename src/{secure_channel => resource/kbs}/native.rs (63%) rename src/{secure_channel => resource/kbs}/ttrpc.rs (78%) rename src/{secure_channel => resource/kbs}/ttrpc_proto/getresource.rs (83%) rename src/{secure_channel => resource/kbs}/ttrpc_proto/getresource_ttrpc.rs (100%) rename src/{secure_channel => resource/kbs}/ttrpc_proto/mod.rs (100%) create mode 100644 src/resource/mod.rs delete mode 100644 src/secure_channel/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 5fecbf9d3..584317868 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,14 @@ async-compression = { version = "0.3.15", features = ["futures-io", "tokio", "gz async-trait = "0.1.56" attestation_agent = { git = "https://github.com/confidential-containers/attestation-agent.git", rev = "55db121", 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 } @@ -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/src/auth/mod.rs b/src/auth/mod.rs index 4965a40d6..f0178f430 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,16 @@ 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. +/// The path can be from different places. Like `path://` or +/// `kbs://`. #[cfg(feature = "getresource")] 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..6dc5d3066 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)] @@ -111,18 +107,6 @@ pub struct Paths { /// 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, } @@ -131,10 +115,6 @@ impl Default for Paths { fn default() -> Self { Self { 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(), } } diff --git a/src/image.rs b/src/image.rs index e60e0abfd..abbd8c80a 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,30 +253,13 @@ 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, &image_digest, &auth, - &self.config.file_paths, + &self.config.file_paths.policy_path, ) .await .map_err(|e| anyhow!("Security validate failed: {:?}", e))?; 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..9d1cfb909 --- /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, sync::Mutex}; + +#[cfg(feature = "getresource")] +pub mod kbs; + +#[cfg(feature = "getresource")] +lazy_static::lazy_static! { + /// SecureChannel + pub static ref SECURE_CHANNEL: Mutex> = { + 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/mechanism/cosign/mod.rs b/src/signature/mechanism/cosign/mod.rs index 78608b800..34eca87b2 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,10 +20,9 @@ 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, }; @@ -54,10 +51,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,16 +58,7 @@ 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) -> Result<()> { Ok(()) } @@ -83,22 +67,6 @@ impl SignScheme for CosignParameters { 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 +128,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 +169,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 { @@ -236,7 +195,6 @@ mod tests { key_path: Some("test_data/signature/cosign/cosign1.pub".into()), key_data: None, signed_identity: None, - cosign_key_dir: crate::config::COSIGN_KEY_DIR.into(), }, "registry.cn-hangzhou.aliyuncs.com/xynnn/cosign:latest", )] @@ -245,7 +203,6 @@ mod tests { key_path: Some("test_data/signature/cosign/cosign1.pub".into()), key_data: None, signed_identity: None, - cosign_key_dir: crate::config::COSIGN_KEY_DIR.into(), }, "registry-1.docker.io/xynnn007/cosign:latest", )] @@ -254,7 +211,6 @@ mod tests { key_path: Some("test_data/signature/cosign/cosign1.pub".into()), key_data: None, signed_identity: None, - cosign_key_dir: crate::config::COSIGN_KEY_DIR.into(), }, "quay.io/kata-containers/confidential-containers:cosign-signed", )] @@ -299,7 +255,6 @@ 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); } diff --git a/src/signature/mechanism/mod.rs b/src/signature/mechanism/mod.rs index cb029cd6e..448319444 100644 --- a/src/signature/mechanism/mod.rs +++ b/src/signature/mechanism/mod.rs @@ -14,14 +14,10 @@ //! 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; -use crate::config::Paths; - use super::image::Image; pub mod cosign; @@ -33,22 +29,7 @@ pub trait SignScheme: Send + Sync { /// Do initialization jobs for this scheme. This may include the following /// * preparing runtime directories for storing signatures, configurations, etc. /// * 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>; + async fn init(&mut self) -> Result<()>; /// 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/mod.rs b/src/signature/mechanism/simple/mod.rs index 133b4bc4a..b89f802a7 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,11 @@ 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 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. @@ -53,18 +45,6 @@ pub struct SimpleParameters { // This field is optional. #[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 - #[serde(skip)] - pub gpg_key_ring: String, } /// Prepare directories for configs and sigstore configs. @@ -84,11 +64,8 @@ async fn prepare_runtime_dirs(sig_store_config_dir: &str) -> Result<()> { 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?; + async fn init(&mut self) -> Result<()> { + prepare_runtime_dirs(crate::config::SIG_STORE_CONFIG_DIR).await?; Ok(()) } @@ -98,33 +75,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,9 +91,11 @@ 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?; diff --git a/src/signature/mod.rs b/src/signature/mod.rs index 9e31b551f..6bd8bb94f 100644 --- a/src/signature/mod.rs +++ b/src/signature/mod.rs @@ -16,18 +16,14 @@ 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 @@ -36,78 +32,10 @@ pub mod getresource { pub async fn allows_image( image_reference: &str, image_digest: &str, - secure_channel: Arc>, auth: &RegistryAuth, - paths: &Paths, + policy_json_path: &str, ) -> 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)) - } -} - -#[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(()); - } + use crate::resource; let reference = oci_distribution::Reference::try_from(image_reference)?; let mut image = Image::default_with_reference(reference); @@ -115,12 +43,13 @@ pub mod no_getresource { // 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 policy_json_string = resource::get_resource(policy_json_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(paths).await?; + scheme.init().await?; } policy 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..5da1d84ec 100644 --- a/src/signature/policy/policy_requirement.rs +++ b/src/signature/policy/policy_requirement.rs @@ -139,27 +139,18 @@ 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(), }), 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(), }), 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(), }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), @@ -168,9 +159,6 @@ 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(), }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), @@ -180,9 +168,6 @@ mod tests { prefix: "example".into(), signed_prefix: "example".into(), }), - sig_store_config_dir: "".into(), - default_sig_store_config_file: "".into(), - gpg_key_ring: "".into(), }), ]; From bf963d282dea92b0446976fb92f29bd294914f3d Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sun, 26 Feb 2023 14:59:44 +0800 Subject: [PATCH 3/8] Signature: apply KBS URI on signature module This commit bring out the sigstore configuration file into ImageClient's config to specify which sigstore configuration file for simple signing will be used. Also, some refactoring is applied on the simple signing related codes, including: - Now when merging two sigstore configs, if the contents are duplicated but not conflict, no error will be raised Besides, `KeyPath` field in both cosign and simple signing will use either kbs resource uri or local filesystem path from now on. If a local fs path is used, a absolute path is recommended Signed-off-by: Xynnn007 --- src/config.rs | 4 + src/image.rs | 2 +- src/signature/README.md | 26 +--- src/signature/mechanism/cosign/mod.rs | 144 ++++++++++++------ src/signature/mechanism/mod.rs | 4 +- src/signature/mechanism/simple/README.md | 8 +- src/signature/mechanism/simple/mod.rs | 96 +++++++----- src/signature/mechanism/simple/sigstore.rs | 83 ++++++---- src/signature/mod.rs | 10 +- src/signature/policy/policy_requirement.rs | 5 + .../{ => get_base_url}/res.yaml | 0 .../sigstore_config/merged_result/res1.yaml | 5 + .../sigstore_config/merged_result/res2.yaml | 5 + .../sigstore_config/merged_result/res3.yaml | 7 + 14 files changed, 245 insertions(+), 154 deletions(-) rename test_data/signature/sigstore_config/{ => get_base_url}/res.yaml (100%) create mode 100644 test_data/signature/sigstore_config/merged_result/res1.yaml create mode 100644 test_data/signature/sigstore_config/merged_result/res2.yaml create mode 100644 test_data/signature/sigstore_config/merged_result/res3.yaml diff --git a/src/config.rs b/src/config.rs index 6dc5d3066..4ddabb6f2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -104,6 +104,9 @@ 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, @@ -114,6 +117,7 @@ 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(), auth_file: AUTH_FILE_PATH.into(), } diff --git a/src/image.rs b/src/image.rs index abbd8c80a..cb10c4366 100644 --- a/src/image.rs +++ b/src/image.rs @@ -259,7 +259,7 @@ impl ImageClient { image_url, &image_digest, &auth, - &self.config.file_paths.policy_path, + &self.config.file_paths, ) .await .map_err(|e| anyhow!("Security validate failed: {:?}", e))?; 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 34eca87b2..7d4249ee3 100644 --- a/src/signature/mechanism/cosign/mod.rs +++ b/src/signature/mechanism/cosign/mod.rs @@ -24,7 +24,8 @@ use sigstore::{ use super::SignScheme; 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 @@ -58,12 +59,12 @@ impl SignScheme for CosignParameters { /// This initialization will: /// * Create [`COSIGN_KEY_DIR`] if not exist. #[cfg(feature = "signature-cosign")] - async fn init(&mut self) -> Result<()> { + 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(()) } @@ -192,7 +193,15 @@ 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, }, @@ -200,7 +209,15 @@ mod tests { )] #[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, }, @@ -208,7 +225,15 @@ mod tests { )] #[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, }, @@ -226,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, ); } @@ -261,75 +287,93 @@ mod tests { #[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 448319444..1d1121ae2 100644 --- a/src/signature/mechanism/mod.rs +++ b/src/signature/mechanism/mod.rs @@ -18,6 +18,8 @@ use anyhow::*; use async_trait::async_trait; use oci_distribution::secrets::RegistryAuth; +use crate::config::Paths; + use super::image::Image; pub mod cosign; @@ -29,7 +31,7 @@ pub trait SignScheme: Send + Sync { /// Do initialization jobs for this scheme. This may include the following /// * preparing runtime directories for storing signatures, configurations, etc. /// * gathering necessary files. - async fn init(&mut self) -> Result<()>; + async fn init(&mut self, config: &Paths) -> Result<()>; /// 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 b89f802a7..d99867364 100644 --- a/src/signature/mechanism/simple/mod.rs +++ b/src/signature/mechanism/simple/mod.rs @@ -15,8 +15,13 @@ mod sigstore; #[cfg(feature = "signature-simple")] mod verify; -use crate::signature::image::Image; -use crate::signature::policy::ref_match::PolicyReqMatchType; +use crate::{ + resource, + signature::{ + image::Image, mechanism::simple::sigstore::SigstoreConfig, mechanism::Paths, + policy::ref_match::PolicyReqMatchType, + }, +}; use super::SignScheme; @@ -45,6 +50,10 @@ pub struct SimpleParameters { // This field is optional. #[serde(default, rename = "signedIdentity")] pub signed_identity: Option, + + /// Sigstore config file + #[serde(skip)] + pub(crate) sig_store_config_file: SigstoreConfig, } /// Prepare directories for configs and sigstore configs. @@ -64,9 +73,14 @@ async fn prepare_runtime_dirs(sig_store_config_dir: &str) -> Result<()> { impl SignScheme for SimpleParameters { /// Init simple scheme signing #[cfg(feature = "signature-simple")] - async fn init(&mut self) -> Result<()> { + async fn init(&mut self, config: &Paths) -> Result<()> { prepare_runtime_dirs(crate::config::SIG_STORE_CONFIG_DIR).await?; - + self.initialize_sigstore_config().await?; + let sig_store_config_file = 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,7 +112,7 @@ impl SignScheme for SimpleParameters { } }; - 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() { @@ -129,7 +143,7 @@ impl SignScheme for SimpleParameters { } #[cfg(not(feature = "signature-simple"))] - async fn allows_image(&self, _image: &mut Image, _auth: &RegistryAuth) -> Result<()> { + async fn allows_image(&mut self, _image: &mut Image, _auth: &RegistryAuth) -> Result<()> { bail!("feature \"signature-simple\" not enabled.") } } @@ -168,40 +182,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)?; + + Ok(()) + } - // Format the sigstore name: `image-repository@digest-algorithm=digest-value`. - let sigstore_name = sigstore::format_sigstore_name(&image.reference, image_digest); + 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"); + }; - // 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?; + // 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/mod.rs b/src/signature/mod.rs index 6bd8bb94f..112a975e0 100644 --- a/src/signature/mod.rs +++ b/src/signature/mod.rs @@ -16,7 +16,7 @@ pub use no_getresource::allows_image; #[cfg(feature = "getresource")] pub mod getresource { - use crate::signature::policy::Policy; + use crate::{config::Paths, signature::policy::Policy}; use super::image::Image; @@ -28,12 +28,12 @@ pub mod getresource { /// `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"))] + #[cfg(feature = "signature")] pub async fn allows_image( image_reference: &str, image_digest: &str, auth: &RegistryAuth, - policy_json_path: &str, + file_paths: &Paths, ) -> Result<()> { use crate::resource; @@ -43,13 +43,13 @@ pub mod getresource { // 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(policy_json_path).await?; + 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().await?; + scheme.init(file_paths).await?; } policy diff --git a/src/signature/policy/policy_requirement.rs b/src/signature/policy/policy_requirement.rs index 5da1d84ec..0f3ae0cbe 100644 --- a/src/signature/policy/policy_requirement.rs +++ b/src/signature/policy/policy_requirement.rs @@ -139,18 +139,21 @@ mod tests { key_path: Some("/keys/public-gpg-keyring".into()), key_data: None, signed_identity: None, + ..Default::default() }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), key_path: None, key_data: Some("bm9uc2Vuc2U=".into()), signed_identity: None, + ..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), + ..Default::default() }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), @@ -159,6 +162,7 @@ mod tests { signed_identity: Some(PolicyReqMatchType::ExactReference { docker_reference: "docker.io/example/busybox:latest".into(), }), + ..Default::default() }), PolicyReqType::SimpleSigning(SimpleParameters { key_type: "GPGKeys".into(), @@ -168,6 +172,7 @@ mod tests { prefix: "example".into(), signed_prefix: "example".into(), }), + ..Default::default() }), ]; 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 From a59d9e4d80ad23f63ba856d535ff80d7d0da12b6 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sun, 26 Feb 2023 15:05:12 +0800 Subject: [PATCH 4/8] test: integration test update for resource uri Credential in integration test now is specified by a kbs resource uri (kbs resource uri in `get_resource` api test) Image decryption key in integration is specified by a kbs resource uri in image's AnnotationPacket (kbs resource uri in `unwrap_key` api test) Signed-off-by: Xynnn007 --- scripts/build_attestation_agent.sh | 2 +- .../aa-offline_fs_kbc-keys.json | 20 ++++++------- ...aa-offline_fs_kbc-resources-no-cosign.json | 7 ----- .../aa-offline_fs_kbc-resources.json | 10 +++---- tests/common/mod.rs | 1 - tests/credential.rs | 9 ++++-- tests/image_decryption.rs | 29 ++++--------------- tests/signature_verification.rs | 12 ++++++++ 8 files changed, 40 insertions(+), 50 deletions(-) delete mode 100644 test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-no-cosign.json 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/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/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 From 6157cae87bed1dacae04c351dfcce6630636d457 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sun, 26 Feb 2023 15:48:28 +0800 Subject: [PATCH 5/8] docs: update docs for integration test Signed-off-by: Xynnn007 --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From d579c93a5c0d7d148c1bf4586f449d1dde6d73b3 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sun, 26 Feb 2023 16:16:17 +0800 Subject: [PATCH 6/8] lint: fix clippy errors Signed-off-by: Xynnn007 --- src/auth/mod.rs | 1 - src/resource/mod.rs | 6 +- src/signature/mechanism/simple/mod.rs | 17 ++---- src/signature/mod.rs | 84 ++++++++++++--------------- 4 files changed, 45 insertions(+), 63 deletions(-) diff --git a/src/auth/mod.rs b/src/auth/mod.rs index f0178f430..42bcd5a33 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -28,7 +28,6 @@ pub struct DockerAuthConfig { /// Get a credential (RegistryAuth) for the given Reference. /// The path can be from different places. Like `path://` or /// `kbs://`. -#[cfg(feature = "getresource")] pub async fn credential_for_reference( reference: &Reference, auth_file_path: &str, diff --git a/src/resource/mod.rs b/src/resource/mod.rs index 9d1cfb909..923d4e6a9 100644 --- a/src/resource/mod.rs +++ b/src/resource/mod.rs @@ -12,7 +12,7 @@ use anyhow::*; use async_trait::async_trait; -use tokio::{fs, sync::Mutex}; +use tokio::fs; #[cfg(feature = "getresource")] pub mod kbs; @@ -20,8 +20,8 @@ pub mod kbs; #[cfg(feature = "getresource")] lazy_static::lazy_static! { /// SecureChannel - pub static ref SECURE_CHANNEL: Mutex> = { - Mutex::new(None) + pub static ref SECURE_CHANNEL: tokio::sync::Mutex> = { + tokio::sync::Mutex::new(None) }; } diff --git a/src/signature/mechanism/simple/mod.rs b/src/signature/mechanism/simple/mod.rs index d99867364..7bbb6fe5a 100644 --- a/src/signature/mechanism/simple/mod.rs +++ b/src/signature/mechanism/simple/mod.rs @@ -15,13 +15,7 @@ mod sigstore; #[cfg(feature = "signature-simple")] mod verify; -use crate::{ - resource, - signature::{ - image::Image, mechanism::simple::sigstore::SigstoreConfig, mechanism::Paths, - policy::ref_match::PolicyReqMatchType, - }, -}; +use crate::signature::{image::Image, mechanism::Paths, policy::ref_match::PolicyReqMatchType}; use super::SignScheme; @@ -52,8 +46,9 @@ pub struct SimpleParameters { pub signed_identity: Option, /// Sigstore config file + #[cfg(feature = "signature-simple")] #[serde(skip)] - pub(crate) sig_store_config_file: SigstoreConfig, + pub(crate) sig_store_config_file: sigstore::SigstoreConfig, } /// Prepare directories for configs and sigstore configs. @@ -76,9 +71,9 @@ impl SignScheme for SimpleParameters { async fn init(&mut self, config: &Paths) -> Result<()> { prepare_runtime_dirs(crate::config::SIG_STORE_CONFIG_DIR).await?; self.initialize_sigstore_config().await?; - let sig_store_config_file = resource::get_resource(&config.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)?; + serde_yaml::from_slice::(&sig_store_config_file)?; self.sig_store_config_file .update_self(sig_store_config_file)?; Ok(()) @@ -143,7 +138,7 @@ impl SignScheme for SimpleParameters { } #[cfg(not(feature = "signature-simple"))] - async fn allows_image(&mut self, _image: &mut Image, _auth: &RegistryAuth) -> Result<()> { + async fn allows_image(&self, _image: &mut Image, _auth: &RegistryAuth) -> Result<()> { bail!("feature \"signature-simple\" not enabled.") } } diff --git a/src/signature/mod.rs b/src/signature/mod.rs index 112a975e0..3ba5641dd 100644 --- a/src/signature/mod.rs +++ b/src/signature/mod.rs @@ -8,53 +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, signature::policy::Policy}; - - use super::image::Image; - - 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; - - 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?; - } - - 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?; } + + policy + .is_image_allowed(image, auth) + .await + .map_err(|e| anyhow::anyhow!("Validate image failed: {:?}", e)) } From d451f1747e6a8c976fa78b641ad10852b1a993d5 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Mon, 27 Feb 2023 11:38:31 +0800 Subject: [PATCH 7/8] fix: wrong init order for ImageConfig test The env should be removed first and then the config be inited, or when the env CC_IMAGE_WORK_DIR is set, the first assert_eq will fail Signed-off-by: Xynnn007 --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 4ddabb6f2..33c26501a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -134,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); From 259770468f42d7e87dbeea002a71ad24f66b8e56 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sun, 26 Feb 2023 16:33:42 +0800 Subject: [PATCH 8/8] dep: update aa and ocicrypt-rs rev related rev brings kbs resource uri scheme Signed-off-by: Xynnn007 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 584317868..6e76dc4a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ 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 } @@ -27,7 +27,7 @@ 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 }