From 92c94fa7bad2f30ae0ae27a21ebff394d99628c9 Mon Sep 17 00:00:00 2001 From: Matthew Arnold Date: Fri, 29 Sep 2023 13:44:35 +0100 Subject: [PATCH] image-rs: Support simple signing with X-R-S-S Support getting simple signing signatures from registry that supports X-R-S-S extension. Refactor into separate tests for XRSS and others. Fixes: confidential-containers#12 Signed-off-by: Matthew Arnold --- .github/workflows/image_rs_build.yml | 7 + Cargo.lock | 57 ++++- image-rs/Cargo.toml | 2 + .../src/signature/mechanism/simple/mod.rs | 221 ++++++++++++++++-- .../aa-offline_fs_kbc-resources-for-icr.json | 7 + image-rs/tests/common/mod.rs | 6 +- image-rs/tests/credential.rs | 2 +- image-rs/tests/image_decryption.rs | 2 +- image-rs/tests/signature_verification.rs | 71 +++++- 9 files changed, 351 insertions(+), 24 deletions(-) create mode 100644 image-rs/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-for-icr.json diff --git a/.github/workflows/image_rs_build.yml b/.github/workflows/image_rs_build.yml index cc6ccf66d..c82e2dec8 100644 --- a/.github/workflows/image_rs_build.yml +++ b/.github/workflows/image_rs_build.yml @@ -74,6 +74,7 @@ jobs: cargo clippy -p image-rs --all-targets --features=kata-cc-native-tls --no-default-features -- -D warnings cargo clippy -p image-rs --all-targets --features=enclave-cc-eaakbc-native-tls --no-default-features -- -D warnings cargo clippy -p image-rs --all-targets --features=enclave-cc-cckbc-native-tls --no-default-features -- -D warnings + cargo clippy -p image-rs --all-targets --features=kata-cc-native-tls,signature-simple-xrss --no-default-features -- -D warnings - name: Run cargo build uses: actions-rs/cargo@v1 @@ -100,3 +101,9 @@ jobs: - name: Run cargo test - kata-cc (native-tls version) with keywrap-ttrpc (default) + keywrap-jwe run: | sudo -E PATH=$PATH -s cargo test -p image-rs --no-default-features --features=kata-cc-native-tls,keywrap-jwe + + - name: Run cargo test - kata-cc (native-tls version) with keywrap-ttrpc (default) + keywrap-jwe and with signatures from XRSS registry extension + env: + AUTH_PASSWORD: ${{ secrets.SH_ICR_API_KEY }} + run: | + sudo -E PATH=$PATH -s cargo test -p image-rs --no-default-features --features=kata-cc-native-tls,keywrap-jwe,signature-simple-xrss diff --git a/Cargo.lock b/Cargo.lock index 1d8ffe52a..6a2e125cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,7 @@ dependencies = [ "serde_json", "strum", "tokio", + "toml 0.8.1", "tonic", ] @@ -2472,6 +2473,7 @@ dependencies = [ "openssl", "prost 0.11.9", "protobuf 3.2.0", + "reqwest", "rstest", "sequoia-openpgp", "serde", @@ -2822,6 +2824,7 @@ dependencies = [ "strum", "thiserror", "tokio", + "toml 0.8.1", "tonic", "tonic-build", "uuid", @@ -3289,7 +3292,7 @@ dependencies = [ "log", "serde", "serde_json", - "toml", + "toml 0.5.11", ] [[package]] @@ -5047,6 +5050,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5800,6 +5812,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc1433177506450fe920e46a4f9812d0c211f5dd556da10e731a0a3dfa151f0" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.9.2" @@ -6543,6 +6589,15 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/image-rs/Cargo.toml b/image-rs/Cargo.toml index 66b78249a..d599bad79 100644 --- a/image-rs/Cargo.toml +++ b/image-rs/Cargo.toml @@ -32,6 +32,7 @@ oci-spec = "0.6.2" ocicrypt-rs = { path = "../ocicrypt-rs", default-features = false, features = ["async-io"], optional = true } prost = { workspace = true, optional = true } protobuf = { workspace = true, optional = true } +reqwest = { workspace = true, features = ["json"], optional = true } sequoia-openpgp = { version = "1.7.0", default-features = false, features = ["compression", "crypto-rust", "allow-experimental-crypto", "allow-variable-time-crypto"], optional = true } serde = { workspace = true, features = ["serde_derive", "rc"] } serde_json.workspace = true @@ -111,6 +112,7 @@ signature-cosign-native = ["signature-cosign", "sigstore/cosign-native-tls"] oci-distribution-rustls = ["oci-distribution/rustls-tls"] oci-distribution-native = ["oci-distribution/native-tls"] +signature-simple-xrss = ["signature-simple", "dep:reqwest"] signature-simple = ["signature", "sequoia-openpgp", "serde_yaml"] snapshot-overlayfs = ["nix"] diff --git a/image-rs/src/signature/mechanism/simple/mod.rs b/image-rs/src/signature/mechanism/simple/mod.rs index 7c60d578c..b55824c4c 100644 --- a/image-rs/src/signature/mechanism/simple/mod.rs +++ b/image-rs/src/signature/mechanism/simple/mod.rs @@ -10,6 +10,17 @@ use serde::*; use strum_macros::Display; use strum_macros::EnumString; +#[cfg(feature = "signature-simple")] +use base64::Engine; + +#[cfg(feature = "signature-simple-xrss")] +use oci_distribution::Reference; +#[cfg(feature = "signature-simple-xrss")] +use reqwest::Client; + +#[cfg(feature = "signature-simple-xrss")] +use crate::signature::image::digest::Digest; + #[cfg(feature = "signature-simple")] mod sigstore; #[cfg(feature = "signature-simple")] @@ -51,6 +62,46 @@ pub struct SimpleParameters { pub(crate) sig_store_config_file: sigstore::SigstoreConfig, } +#[cfg(feature = "signature-simple-xrss")] +#[derive(Debug, Default)] +pub struct RegistryClient { + // reqwest client for the container registry that supports the X-R-S-S extension + pub client: Client, +} + +#[cfg(feature = "signature-simple-xrss")] +#[derive(Deserialize, Debug, PartialEq, Eq, Serialize, Default)] +pub struct GetOAuthTokenResponse { + #[serde(default)] + pub token: String, + #[serde(default)] + pub access_token: String, + #[serde(default)] + pub expires_in: u32, + #[serde(default)] + pub issued_at: String, +} + +#[cfg(feature = "signature-simple-xrss")] +#[derive(Deserialize, Debug, PartialEq, Eq, Serialize, Default)] +pub struct GetSignaturesResponse { + #[serde(default)] + signatures: Vec, +} + +#[cfg(feature = "signature-simple-xrss")] +#[derive(Deserialize, Debug, PartialEq, Eq, Serialize, Default)] +pub struct RegistrySignature { + #[serde(default, rename = "schemaVersion")] + pub schema_version: u32, + #[serde(default)] + pub name: String, + #[serde(default, rename = "type")] + pub signature_type: String, + #[serde(default)] + pub content: String, +} + /// Prepare directories for configs and sigstore configs. /// It will create (if not) the following dirs: /// * [`SIG_STORE_CONFIG_DIR`] @@ -71,9 +122,16 @@ 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 = crate::resource::get_resource(&config.sigstore_config).await?; + let sig_store_config_file = crate::resource::get_resource(&config.sigstore_config).await; + + #[cfg(feature = "signature-simple-xrss")] + if sig_store_config_file.is_err() { + // When using xrss extension a sigstore config file is optional + return Ok(()); + } + 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(()) @@ -86,7 +144,6 @@ impl SignScheme for SimpleParameters { #[cfg(feature = "signature-simple")] async fn allows_image(&self, image: &mut Image, _auth: &RegistryAuth) -> Result<()> { - use base64::Engine; // FIXME: only support "GPGKeys" type now. // // refer to https://github.com/confidential-containers/image-rs/issues/14 @@ -108,7 +165,7 @@ impl SignScheme for SimpleParameters { } }; - let sigs = self.get_signatures(image).await?; + let sigs = self.get_signatures(image, _auth).await?; let mut reject_reasons: Vec = Vec::new(); for sig in sigs.iter() { @@ -177,19 +234,129 @@ pub fn judge_single_signature( Ok(()) } +#[cfg(feature = "signature-simple-xrss")] +impl RegistryClient { + pub fn new() -> RegistryClient { + RegistryClient { + client: reqwest::Client::new(), + } + } + + pub async fn get_oauth_token_for_registry( + &self, + image: &Image, + auth: &RegistryAuth, + ) -> Result { + match auth { + RegistryAuth::Anonymous => { + bail!("Trying to get signature from a registry without providing auth information") + } + RegistryAuth::Basic(username, password) => { + let scope = format!("repository:{}:pull", image.reference.repository()); + let res = self + .client + .get(format!( + "https://{}/oauth/token", + image.reference.registry() + )) + .query(&[ + ("account", username), + ("scope", &scope), + ("service", &"registry".to_string()), + ]) + .basic_auth(username, Some(password)) + .send() + .await + .map_err(|e| anyhow!("Failed to fetch oauth token for registry: {:?}", e))?; + + let oauth_token_response_body: GetOAuthTokenResponse = + res.json::().await.map_err(|e| { + anyhow!( + "Unexpected response from fetching oauth token from registry: {:?}", + e + ) + })?; + + Ok(oauth_token_response_body.token) + } + } + } + + pub async fn get_signatures_from_registry( + &self, + image: &Image, + digest: &Digest, + auth: &RegistryAuth, + ) -> Result>> { + let mut sigs: Vec> = new_sigs_vec().await; + + let res = self + .client + .get(format!("https://{}/v2/", image.reference.registry())) + .send() + .await + .map_err(|e| { + anyhow!( + "Failed to query extensions supported by registry v2 endpoint: {:?}", + e + ) + })?; + + let registry_signature_support_header = res.headers().get("x-registry-supports-signatures"); + if registry_signature_support_header.is_some() + && registry_signature_support_header.unwrap() == "1" + { + let oauth_token = self.get_oauth_token_for_registry(image, auth).await?; + let res = self + .client + .get(format_registry_signatures_extension_url( + &image.reference, + digest, + )) + .bearer_auth(oauth_token) + .send() + .await + .map_err(|e| anyhow!("Failed to get signatures from registry: {:?}", e))?; + + let signatures_response_body: GetSignaturesResponse = + res.json::().await.map_err(|e| { + anyhow!( + "Unexpected response from fetching signatures from registry: {:?}", + e + ) + })?; + + for registry_signature in signatures_response_body.signatures.iter() { + let signature = base64::engine::general_purpose::STANDARD.decode(®istry_signature.content).map_err(|e| { + anyhow!( + "Error when decoding sognature from registry. Signature is not base64 encoded: {:?}", + e + ) + })?; + sigs.push(signature); + } + } + + Ok(sigs) + } +} + +#[cfg(feature = "signature-simple-xrss")] +pub fn format_registry_signatures_extension_url(image_ref: &Reference, digest: &Digest) -> String { + format!( + "https://{}/extensions/v2/{}/signatures/{}:{}", + image_ref.registry(), + image_ref.repository(), + digest.algorithm(), + digest.value() + ) +} + #[cfg(feature = "signature-simple")] 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)?; @@ -197,7 +364,12 @@ impl SimpleParameters { Ok(()) } - pub async fn get_signatures(&self, image: &Image) -> Result>> { + pub async fn get_signatures( + &self, + image: &Image, + _auth: &RegistryAuth, + ) -> Result>> { + let mut sigs: Vec> = new_sigs_vec().await; // Get image digest (manifest digest) let image_digest = if !image.manifest_digest.is_empty() { image.manifest_digest.clone() @@ -207,6 +379,22 @@ impl SimpleParameters { bail!("Missing image digest"); }; + #[cfg(feature = "signature-simple-xrss")] + { + let registry_client = RegistryClient::new(); + let mut registry_sigs = registry_client + .get_signatures_from_registry(image, &image_digest, _auth) + .await?; + sigs.append(&mut registry_sigs); + if self.sig_store_config_file == sigstore::SigstoreConfig::default() { + if sigs.is_empty() { + bail!("Missing sigstore config file and no signatures in registry"); + } + + return Ok(sigs); + } + } + // Format the sigstore name: `image-repository@digest-algorithm=digest-value`. let sigstore_name = sigstore::format_sigstore_name(&image.reference, image_digest); @@ -219,8 +407,13 @@ impl SimpleParameters { 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 mut sigstore_sigs = sigstore::get_sigs_from_specific_sigstore(sigstore_uri).await?; + sigs.append(&mut sigstore_sigs); Ok(sigs) } } + +pub async fn new_sigs_vec() -> Vec> { + Vec::new() +} diff --git a/image-rs/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-for-icr.json b/image-rs/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-for-icr.json new file mode 100644 index 000000000..f4e40b731 --- /dev/null +++ b/image-rs/test_data/offline-fs-kbc/aa-offline_fs_kbc-resources-for-icr.json @@ -0,0 +1,7 @@ +{ + "default/security-policy/test": "ewogICAgImRlZmF1bHQiOiBbCiAgICAgICAgewogICAgICAgICAgICAidHlwZSI6ICJpbnNlY3VyZUFjY2VwdEFueXRoaW5nIgogICAgICAgIH0KICAgIF0sCiAgICAidHJhbnNwb3J0cyI6IHsKICAgICAgICAiZG9ja2VyIjogewogICAgICAgICAgICAidWsuaWNyLmlvL21hdHRhcm5vX2ltYWdlX3B1c2gvYnVzeWJveCI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJzaWduZWRCeSIsCiAgICAgICAgICAgICAgICAgICAgImtleVR5cGUiOiAiR1BHS2V5cyIsCiAgICAgICAgICAgICAgICAgICAgImtleVBhdGgiOiAia2JzOi8vL2RlZmF1bHQvZ3BnLXB1YmxpYy1rZXkvdGVzdCIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgInF1YXkuaW8va2F0YS1jb250YWluZXJzL2NvbmZpZGVudGlhbC1jb250YWluZXJzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJ0eXBlIjogInNpZ25lZEJ5IiwKICAgICAgICAgICAgICAgICAgICAia2V5VHlwZSI6ICJHUEdLZXlzIiwKICAgICAgICAgICAgICAgICAgICAia2V5UGF0aCI6ICJrYnM6Ly8vZGVmYXVsdC9ncGctcHVibGljLWtleS90ZXN0IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICBdCiAgICAgICAgfQogICAgfQp9Cg==", + "default/sigstore-config/test": "", + "default/gpg-public-key/test": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCgptUUdOQkdUd2MzRUJEQURvbHowcmxwTGRkR2R1SENaZERnSEd1Y1lKeFh0elAwRE9zMkEvOTlKN1lhd3VjcTNSCmhlaHVUcmdRWkhpNCtmM28zU3V4QmUzeFdSc1F5dDRCbDBaVFJ6bmdqdXoyTWFxNzV4M0RNSUYzMElSbzlkcm4KWVg5T0ZGaXJMTTRwT0orY0pJaExJb1EyZXk2YnNwNGdpQnVGYlVYQ255T2dEQSs2cVNZMm5zR1R4TmdSam5wbgpWd28zc2tOaitjS21wUzdEbkpFeGZuL25jZDJrR0pOQXBHa1gzSDR2dXd0dUIrMnhQR21nL1U0QWdSeDZxKzErCm1rZGNIMFFWdW9tQ2FJUTFxRzMybTgxaG8xWTBlWlpMbVUycmVtNThqSkNUU0xEMkVHKzd3WTVDMkRETWlNRG0KSGhhOGoyTjdMUWJmVERwZVd4Z0lxVjlHWUl6cGc1MUtLcW1wVEFDOXkwRFZkbzlYNGlIZDFjYkNUTkFhNGtGQwpNUUkwK1BQT0hhVWRoUzE3S2h6dmwzSm11bTkvMWtyNWdCMmd2ajNXUHdMSzZxMzRZWmExdXVnc1ZkOHpzQ001CjlaWHNFUlhiam5neGpJWUJocUMzV2N3bXprNjRPK0ZjM1JKby80K2x3eFhaNXlTNCtMV25mMDNkblVKdzRIZVoKWldGZUdFdSsvMzNMSlhjQUVRRUFBYlFrVFdGMGRHaGxkeUJCY201dmJHUWdQRzFoZEhSaGNtNXZRSFZyTG1saQpiUzVqYjIwK2lRSFVCQk1CQ2dBK0ZpRUVYYjRnM3Q0azJPcmVyb3d2aGt0NFRPWmhKUFlGQW1Ud2MzRUNHd01GCkNRUENad0FGQ3drSUJ3SUdGUW9KQ0FzQ0JCWUNBd0VDSGdFQ0Y0QUFDZ2tRaGt0NFRPWmhKUGJWend3QXFDZUwKd2hzL3h0ek1uSG9aMk5BZys0TTl5elQ3RVZTOUo3MDgxTmFHdjFBckQvUEtUcTNQcCtXbm5XK2VwMlNxUWkzSgp1cjUrWlNSZ1BDYmpVOFRpVGhFRXU0MzFONEZsZnhCOGdORWNtOXRYTFZLMDJFU2Y5VnloTGZmZ2wrbHRpa1AyCk1DaXBrdmVaUGN3MUtnZVNyUnc2a1FRcXFadEN1dlM2Vm9lbGxSTEdvV0hoaHpGYXRmblpnNDVEZ1k1ZVVxNXYKSmczUHlKSDB4Mkhqa1Z4K0Q3QWRZSDRhT29oNGJhd2JXcHQrRERWZjBoUEJFZEtlR0ZGZEQ4OW1FSExDRnNCOQplTUF2cDU2ZDZxTUV5a0VWQ3dYMnVwQmhzc1RQdDBvdEZheFh2WGNJZXZLL05uOXNlbFozNWZETmVlaDZyMHduCm8rV3VURjBmWXNqN2k2MlZxa0hZZkR1eHhjZ09qY1RLeE5pTVB1eWNSOTJuZUdmVzVkK3FBaDQ5bDRrZVgxU0sKSTVDTi9ITFJwS1NjMUVaRDBVYXZjbjFLOHZ6NHVVRTBBbVM5dVB4OVdJMExoSkZiYlBseGNma2dBVFBhQUtrZQo5ZFc0VGZScVoycHFWQzB3dXdXck1nQVA1blJ0eWNqb1d3U3IxcXY1NDNqSkJEait4aGIwTmFLaUQ1Rnd1UUdOCkJHVHdjM0VCREFEU3I3K2RWcUdkS1R2dDV5Mi9oZzVHYkRKZlh4UUFqM0VnZmNvUXMzUWJkdDBmbVkwb0xGTXkKekpiS1BwTTRhS3dZdXE4YmszQkpMKyszUUZOU0tmd09IVEJBYWZWQWttVk5MU1NkN3hTNm1LTlUxcUZva2x2agp6U0VsRnVhVFhWUmlma01nWkdMS1NubExYOVlUN0xGb2piTGF5OHBEWFFreDFxSjN0SFBURTk5L00xN3J6MTlICkpEdnhkcnRTcXZldkRleVA3OGMvZTFpKzZ1bnEwaU1lUHZlNXN4WVA4RXRmcHJzeEYvQldBaEQ2ZnVHbnBhcXQKZGRSR0FEcnhQaFBrMCtLTGtSM1NxZGdjTGlyRWN5UEpyR3lwdlBDemNmTjZ6OVZOdk55M1BaYWs5UXB0OTBGbQprR2hUM1lCbjZnOFpBdTRZcVMzeEtWcTB6dzhVbXdra2VFTkNydU9rTG41Rk83SnRQdkVjN1J0amtSZW00d2dtCklORXJ4NU9zTzJUM2Z1QktXVklpOGpOdjBQYWowZk9VTHgxSzFtVzFSbVpIMmRHa0xTWmJzYXJxV1gwSW04MjMKeEsvdzFWN3owRXZuNHcraXZUVDUzQ29XMldNNndZTlY1TStFV2FMZjBHVmRCRDhZVlRkdGhwZlZwQjdvM2ZlWgpBelk4TkdHVWtUY0FFUUVBQVlrQnZBUVlBUW9BSmhZaEJGMitJTjdlSk5qcTNxNk1MNFpMZUV6bVlTVDJCUUprCjhITnhBaHNNQlFrRHdtY0FBQW9KRUlaTGVFem1ZU1QyS0NFTUFOY2NLQVJUSWdvcHBHTTBmNktsK2RNQ1cyd2UKcWFCdkNESzhvTUhJdDBMTWpMbDJvMEczZDI2Q0ZpaXVFazJlS0lFWkxDUjhyWFhXK2FBVmxsZHo2RzZMSThUZgpTUTA2c3R5dXROQTFNNmUyY1NxU0FRWWVOYXVwdkRaM0Y3a0M4WnEzeEYzOEZ0ckhHUHRrT0NwS0lxbzJ3THUrCloyTjZzait4cW1lMTA4aWFkc3hXbEc4K0lIR0FuWEtPcjZiOHpULzRGZVdXVCsxcnpDY01nREgwS3BkSkZ6OC8KTEZrQUsvTWYyQWlwUGltUU0rblJvT2grNlhwWExuNjlqSmNXREY4Wmw1YXNyL3pmZUtVWUNEUi9RRGpEblFMdwo3ekdETi81REtPSmNBcmlqN2lyU3FISkhRRW5xaDllOFdWUFJMTzBMcENsa2xwUlhmUk1lSVdJTnNkQVgxeEpWCkZVUUt1dUFoWkVwOWZFWmdlaVFadVZreFlYa0ptTWwrYzg0K2pBSkVjNmRjOVhIUTFxd0lSQUgvOTdScitzYXMKZFNOTUtuSkM1dGdFb0tINWloeklVcnZjV0tTcnNkaHdkcmovSDF6QTN0WmNSN3VLeVp6TEorZ05VeElDTUppeAp3djNVdUkzNUU2QkdCTWlySXcwL3UzbS8rdHZDTkFuWmE4YitjQT09Cj1ocjVvCi0tLS0tRU5EIFBHUCBQVUJMSUMgS0VZIEJMT0NLLS0tLS0K", + "default/cosign-public-key/test": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFMWdIR2JmazFBcU93ZUxFTThIZlQwYm1mUUUzYgo5ZmNwL0xVNzVGTWZ4VlpYbU5WdFVwcnNITTF0aHV1aUJLT29mdjhLVjdUckZsNHA4TkpDaVhVa2hBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==", + "default/credential/test": "ewogICAgImF1dGhzIjogewogICAgICAgICJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0sCiAgICAgICAgInF1YXkuaW8iOiB7CiAgICAgICAgICAgICJhdXRoIjogImJHbDFaR0ZzYVdKcU9sQmhjM04zTUhKa0lYRmhlZ289IgogICAgICAgIH0KICAgIH0KfQ==" +} diff --git a/image-rs/tests/common/mod.rs b/image-rs/tests/common/mod.rs index f587073b5..2252a0a44 100644 --- a/image-rs/tests/common/mod.rs +++ b/image-rs/tests/common/mod.rs @@ -18,9 +18,7 @@ const OFFLINE_FS_KBC_RESOURCE_SCRIPT: &str = "scripts/install_offline_fs_kbc_fil /// Attestation Agent Key Provider Parameter pub const AA_PARAMETER: &str = "provider:attestation-agent:offline_fs_kbc::null"; -const OFFLINE_FS_KBC_RESOURCE: &str = "aa-offline_fs_kbc-resources.json"; - -pub async fn prepare_test() { +pub async fn prepare_test(offline_fs_kbc_resources: &str) { // Check whether is in root privilege assert!( nix::unistd::Uid::effective().is_root(), @@ -36,7 +34,7 @@ pub async fn prepare_test() { Command::new(OFFLINE_FS_KBC_RESOURCE_SCRIPT) .arg("install") - .arg(OFFLINE_FS_KBC_RESOURCE) + .arg(offline_fs_kbc_resources) .output() .await .expect("Install offline-fs-kbcs's resources failed."); diff --git a/image-rs/tests/credential.rs b/image-rs/tests/credential.rs index 0d021f09f..acea9a0b4 100644 --- a/image-rs/tests/credential.rs +++ b/image-rs/tests/credential.rs @@ -19,7 +19,7 @@ pub mod common; #[tokio::test] #[serial] async fn test_use_credential(#[case] image_ref: &str, #[case] auth_file_uri: &str) { - common::prepare_test().await; + common::prepare_test("aa-offline_fs_kbc-resources.json").await; // Init AA let _aa = common::start_attestation_agent() diff --git a/image-rs/tests/image_decryption.rs b/image-rs/tests/image_decryption.rs index 65a57b983..d88db5eb8 100644 --- a/image-rs/tests/image_decryption.rs +++ b/image-rs/tests/image_decryption.rs @@ -30,7 +30,7 @@ const OCICRYPT_CONFIG: &str = "test_data/ocicrypt_keyprovider_ttrpc.conf"; #[tokio::test] #[serial] async fn test_decrypt_layers(#[case] image: &str) { - common::prepare_test().await; + common::prepare_test("aa-offline_fs_kbc-resources.json").await; // Init AA let _aa = common::start_attestation_agent() .await diff --git a/image-rs/tests/signature_verification.rs b/image-rs/tests/signature_verification.rs index f1da138f2..b11fcc307 100644 --- a/image-rs/tests/signature_verification.rs +++ b/image-rs/tests/signature_verification.rs @@ -36,6 +36,9 @@ const _TEST_ITEMS: usize = cfg!(feature = "signature-cosign") as usize * 2 + cfg!(feature = "signature-simple") as usize * 2 + 2; +#[cfg(feature = "signature-simple-xrss")] +const _TEST_ITEMS_XRSS: usize = 3; + /// Four test cases. const _TESTS: [_TestItem; _TEST_ITEMS] = [ _TestItem { @@ -80,6 +83,28 @@ const _TESTS: [_TestItem; _TEST_ITEMS] = [ }, ]; +#[cfg(feature = "signature-simple-xrss")] +const _TESTS_XRSS: [_TestItem; _TEST_ITEMS_XRSS] = [ + _TestItem { + image_ref: "quay.io/kata-containers/confidential-containers:signed", + allow: false, + signing_scheme: SigningName::SimpleSigning, + description: "Deny pulling an unencrypted signed image with no local sigstore and a registry that does not support the X-R-S-S API extension", + }, + _TestItem { + image_ref: "uk.icr.io/mattarno_image_push/busybox:signed-latest", + allow: true, + signing_scheme: SigningName::SimpleSigning, + description: "Allow pulling an unencrypted signed image from a protected registry that supports the X-R-S-S API extension with no local sigstore", + }, + _TestItem { + image_ref: "uk.icr.io/mattarno_image_push/busybox:unsigned-1.35", + allow: false, + signing_scheme: SigningName::SimpleSigning, + description: "Deny pulling an unencrypted and unsigned image from a protected registry that supports the X-R-S-S API extension with no local sigstore", + }, +]; + #[cfg(feature = "getresource")] const POLICY_URI: &str = "kbs:///default/security-policy/test"; @@ -93,13 +118,53 @@ const SIGSTORE_CONFIG_URI: &str = "kbs:///default/sigstore-config/test"; #[tokio::test] #[serial] async fn signature_verification() { - common::prepare_test().await; + do_signature_verification_tests(&_TESTS, "aa-offline_fs_kbc-resources.json", &None).await; +} + +#[cfg(all(feature = "signature-simple-xrss", feature = "getresource"))] +#[tokio::test] +#[serial] +async fn signature_verification_xrss() { + match std::env::var("AUTH_PASSWORD") { + Ok(auth_password) => match !auth_password.is_empty() { + true => { + let auth = format!("iamapikey:{}", auth_password); + let auth_info = &Some(auth.as_str()); + do_signature_verification_tests( + &_TESTS_XRSS, + "aa-offline_fs_kbc-resources-for-icr.json", + auth_info, + ) + .await; + } + false => { + println!("Skipping xrss test cases because the test cases require authentication and no AUTH is set in the environment"); + } + }, + Err(_e) => { + println!("Skipping xrss test cases because the test cases require authentication and no AUTH is set in the environment"); + } + } +} + +#[cfg(feature = "getresource")] +async fn do_signature_verification_tests( + tests: &[_TestItem<'_, '_>], + offline_fs_kbc_resources: &str, + auth_info: &Option<&str>, +) { + common::prepare_test(offline_fs_kbc_resources).await; // Init AA let _aa = common::start_attestation_agent() .await .expect("Failed to start attestation agent!"); - for test in &_TESTS { + for test in tests { + let mut test_auth_info = auth_info; + if test.image_ref.to_string().contains("quay") { + test_auth_info = &None; + } + // clean former test files common::clean_configs() .await @@ -130,7 +195,7 @@ async fn signature_verification() { .pull_image( test.image_ref, bundle_dir.path(), - &None, + test_auth_info, &Some(common::AA_PARAMETER), ) .await;