diff --git a/Cargo.lock b/Cargo.lock index eab9dc22b..4cf312ee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,6 +90,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" dependencies = [ + "actix-macros", "futures-core", "tokio", ] @@ -394,9 +395,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arrayref" @@ -551,7 +552,7 @@ dependencies = [ "serde", "serde_json", "serde_variant", - "serial_test", + "serial_test 0.9.0", "sha2", "shadow-rs", "strum 0.25.0", @@ -1746,6 +1747,16 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -1785,6 +1796,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2652,6 +2676,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "integration-tests" +version = "0.1.0" +dependencies = [ + "actix-rt", + "actix-web", + "anyhow", + "attestation-service", + "env_logger 0.11.5", + "kbs", + "kbs-client", + "log", + "openssl", + "rstest", + "serde_json", + "serial_test 3.2.0", + "tempfile", + "tokio", +] + [[package]] name = "intel-tee-quote-verification-rs" version = "0.3.0" @@ -3007,7 +3051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4100,7 +4144,7 @@ dependencies = [ "rstest", "serde", "serde_json", - "serial_test", + "serial_test 0.9.0", "sha2", "shadow-rs", "sled", @@ -4814,7 +4858,21 @@ dependencies = [ "lazy_static", "log", "parking_lot 0.12.3", - "serial_test_derive", + "serial_test_derive 0.9.0", +] + +[[package]] +name = "serial_test" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot 0.12.3", + "scc", + "serial_test_derive 3.2.0", ] [[package]] @@ -4829,6 +4887,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sev" version = "3.2.0" @@ -5374,9 +5443,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -5905,7 +5974,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "serial_test", + "serial_test 0.9.0", "sev 4.0.0", "sha2", "shadow-rs", diff --git a/Cargo.toml b/Cargo.toml index b87b1d18b..5f33be2c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "rvps", "tools/kbs-client", "deps/verifier", + "integration-tests", ] resolver = "2" diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml new file mode 100644 index 000000000..e8746ea9e --- /dev/null +++ b/integration-tests/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "integration-tests" +version.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +edition.workspace = true + +[dependencies] +kbs = { path = "../kbs" } +attestation-service = { path = "../attestation-service" } +kbs-client = { path = "../tools/kbs-client" } + +actix-web = "4.9.0" +actix-rt = "2.10.0" +anyhow = "1.0.94" +env_logger = "0.11.5" +log = "0.4.22" +openssl = "0.10.68" +rstest.workspace = true +serde_json = "1.0.133" +serial_test = { version = "3.2.0", features = ["async"]} +tempfile = "3.14.0" +tokio = { version = "1.42.0", features = ["full"] } diff --git a/integration-tests/tests/integration.rs b/integration-tests/tests/integration.rs new file mode 100644 index 000000000..824f025b7 --- /dev/null +++ b/integration-tests/tests/integration.rs @@ -0,0 +1,218 @@ +use kbs::admin::config::AdminConfig; +use kbs::attestation::config::{AttestationConfig, AttestationServiceConfig}; +use kbs::config::HttpServerConfig; +use kbs::config::KbsConfig; +use kbs::policy_engine::PolicyEngineConfig; +use kbs::token::AttestationTokenVerifierConfig; +use kbs::ApiServer; + +use kbs::plugins::{ + implementations::{resource::local_fs::LocalFsRepoDesc, RepositoryConfig}, + PluginsConfig, +}; + +use attestation_service::{ + config::Config, + rvps::{RvpsConfig, RvpsCrateConfig}, + token::{ear_broker, simple, AttestationTokenConfig}, +}; + +use anyhow::{bail, Result}; +use log::info; +use openssl::pkey::PKey; +use rstest::rstest; +use serde_json::json; +use serial_test::serial; +use std::io::Write; +use tempfile::{NamedTempFile, TempDir}; + +const KBS_URL: &str = "http://127.0.0.1:8080"; +const SECRET_BYTES: &[u8; 8] = b"shhhhhhh"; +const SECRET_PATH: &str = "default/test/secret"; +const WAIT_TIME: u64 = 3000; + +const ALLOW_ALL_POLICY: &str = " + package policy + allow = true +"; + +const DENY_ALL_POLICY: &str = " + package policy + allow = false +"; + +#[allow(dead_code)] +enum PolicyType { + AllowAll, + DenyAll, + Custom(String), +} + +// Parameters that define test behavior (coming from rstest) +struct TestParameters { + attestation_token_type: String, +} + +// Internal state of tests +struct TestHarness { + kbs_config: KbsConfig, + auth_privkey: String, + + // Future tests will use some parameters at runtime + #[allow(dead_code)] + test_parameters: TestParameters, +} + +impl TestHarness { + fn new(test_parameters: TestParameters) -> Result { + let auth_keypair = PKey::generate_ed25519()?; + let auth_pubkey = String::from_utf8(auth_keypair.public_key_to_pem()?)?; + let auth_privkey = String::from_utf8(auth_keypair.private_key_to_pem_pkcs8()?)?; + + let work_dir = TempDir::new()?; + let resource_dir = TempDir::new()?; + let policy_path = NamedTempFile::new()?; + + let mut auth_pubkey_path = NamedTempFile::new()?; + auth_pubkey_path.write(auth_pubkey.as_bytes())?; + + let attestation_token_config = match &test_parameters.attestation_token_type[..] { + "Ear" => AttestationTokenConfig::Ear(ear_broker::Configuration { + duration_min: 5, + ..Default::default() + }), + "Simple" => AttestationTokenConfig::Simple(simple::Configuration::default()), + _ => bail!("Unknown attestation token type. Must be Simple or Ear"), + }; + + let kbs_config = KbsConfig { + attestation_token: AttestationTokenVerifierConfig { + trusted_certs_paths: vec![], + insecure_key: true, + trusted_jwk_sets: vec![], + extra_teekey_paths: vec![], + }, + attestation_service: AttestationConfig { + attestation_service: AttestationServiceConfig::CoCoASBuiltIn(Config { + work_dir: work_dir.path().to_path_buf(), + rvps_config: RvpsConfig::BuiltIn(RvpsCrateConfig { + store_type: "LocalFs".into(), + store_config: json!({}), + }), + attestation_token_broker: attestation_token_config, + }), + timeout: 5, + }, + http_server: HttpServerConfig { + sockets: vec!["127.0.0.1:8080".parse()?], + private_key: None, + certificate: None, + insecure_http: true, + }, + admin: AdminConfig { + auth_public_key: None, + insecure_api: true, + }, + policy_engine: PolicyEngineConfig { + policy_path: policy_path.path().to_path_buf(), + }, + plugins: vec![PluginsConfig::ResourceStorage(RepositoryConfig::LocalFs( + LocalFsRepoDesc { + dir_path: resource_dir.path().to_str().unwrap().to_string(), + }, + ))], + }; + + Ok(TestHarness { + kbs_config, + auth_privkey, + test_parameters, + }) + } + + async fn run(&self) -> Result<()> { + let api_server = ApiServer::new(self.kbs_config.clone()).await?; + + let kbs_server = api_server.server()?; + let kbs_handle = kbs_server.handle(); + + actix_web::rt::spawn(kbs_server); + + self.wait(); + self.set_secret(SECRET_PATH.to_string(), SECRET_BYTES.as_ref().to_vec()) + .await?; + self.set_policy(PolicyType::AllowAll).await?; + + let secret = self.get_secret(SECRET_PATH.to_string()).await?; + + assert_eq!(secret, SECRET_BYTES); + info!("TEST: test completed succesfully"); + + kbs_handle.stop(true).await; + + Ok(()) + } + + async fn set_policy(&self, policy: PolicyType) -> Result<()> { + info!("TEST: Setting Resource Policy"); + + let policy_bytes = match policy { + PolicyType::AllowAll => ALLOW_ALL_POLICY.as_bytes().to_vec(), + PolicyType::DenyAll => DENY_ALL_POLICY.as_bytes().to_vec(), + PolicyType::Custom(p) => p.into_bytes(), + }; + + kbs_client::set_resource_policy( + KBS_URL, + self.auth_privkey.clone(), + policy_bytes, + // Optional HTTPS certs for KBS + vec![], + ) + .await?; + + Ok(()) + } + + async fn set_secret(&self, secret_path: String, secret_bytes: Vec) -> Result<()> { + info!("TEST: Setting Secret"); + kbs_client::set_resource( + KBS_URL, + self.auth_privkey.clone(), + secret_bytes, + &secret_path, + // Optional HTTPS certs for KBS + vec![], + ) + .await?; + + Ok(()) + } + + async fn get_secret(&self, secret_path: String) -> Result> { + info!("TEST: Getting Secret"); + let resource_bytes = + kbs_client::get_resource_with_attestation(KBS_URL, &secret_path, None, vec![]).await?; + + Ok(resource_bytes) + } + + fn wait(&self) { + let duration = std::time::Duration::from_millis(WAIT_TIME); + std::thread::sleep(duration); + } +} + +#[rstest] +#[case::ear_allow_all(TestParameters{attestation_token_type: "Ear".to_string() })] +#[case::simple_allow_all(TestParameters{attestation_token_type: "Simple".to_string() })] +#[serial] +#[actix_rt::test] +async fn integration(#[case] test_parameters: TestParameters) -> Result<()> { + let _ = env_logger::try_init_from_env(env_logger::Env::new().default_filter_or("debug")); + + let harness = TestHarness::new(test_parameters)?; + harness.run().await?; + + Ok(()) +}