diff --git a/Cargo.lock b/Cargo.lock index 2b2c1275..de702d4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1641,6 +1641,7 @@ name = "rtc_data_service" version = "0.0.1" dependencies = [ "actix", + "actix-http", "actix-rt", "actix-web", "anyhow", diff --git a/rtc_data_service/Cargo.toml b/rtc_data_service/Cargo.toml index 30460e5a..dcff9b00 100644 --- a/rtc_data_service/Cargo.toml +++ b/rtc_data_service/Cargo.toml @@ -39,6 +39,9 @@ insta = "1.7.1" sodalite = "0.4.0" uuid = "0.8.2" +# Only for type name references. (This should match the version used by actix-web.) +actix-http = "3.0.0-beta.6" + [features] test = [] diff --git a/rtc_data_service/tests/ecalls/local_attestation.rs b/rtc_data_service/tests/ecalls/local_attestation.rs new file mode 100644 index 00000000..3a4e1353 --- /dev/null +++ b/rtc_data_service/tests/ecalls/local_attestation.rs @@ -0,0 +1,18 @@ +//! Test ECALL: `local_attestation` + +use sgx_types::sgx_status_t; + +use crate::helpers; + +#[test] +fn test_local_attestation_success() { + let auth_enclave = helpers::init_auth_enclave(); + let data_enclave = helpers::init_data_enclave(); + + let res = data_enclave.local_attestation(auth_enclave.geteid()); + assert_eq!(res, sgx_status_t::SGX_SUCCESS); + + // TODO: Integration test for message sending + // We should consider moving the integration tests for enclave interaction into rtc_uenclave + // since these tests does not need anything from the data_service +} diff --git a/rtc_data_service/tests/ecalls/mod.rs b/rtc_data_service/tests/ecalls/mod.rs new file mode 100644 index 00000000..422adaf7 --- /dev/null +++ b/rtc_data_service/tests/ecalls/mod.rs @@ -0,0 +1,3 @@ +//! ECALL tests + +mod local_attestation; diff --git a/rtc_data_service/tests/exec_token.rs b/rtc_data_service/tests/exec_token.rs deleted file mode 100644 index 96c3ce5f..00000000 --- a/rtc_data_service/tests/exec_token.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Tests for [`rtc_data_service::exec_token`] - -use std::sync::Arc; - -use actix::Actor; -use actix_web::web::Bytes; -use actix_web::App; -use actix_web::{http, test}; - -use rtc_data_service::data_enclave_actor::DataEnclaveActor; -use rtc_data_service::exec_token; -use rtc_uenclave::EnclaveConfig; -use sgx_types::sgx_status_t; - -#[actix_rt::test] -async fn data_service_exec_token_ok() { - // TODO: Split this test into re-usable components - let app = test::init_service( - App::new() - .data( - DataEnclaveActor::new(Arc::new(rtc_uenclave::EnclaveConfig { - lib_path: "/root/rtc-data/rtc_data_enclave/build/bin/enclave.signed.so" - .to_string(), - ..Default::default() - })) - .start(), - ) - .service(exec_token::req_exec_token), - ) - .await; - - // Call the endpoint - - // TODO: Placeholder request data, for now. - let req_body = exec_token::models::RequestBody { - metadata: exec_token::models::Metadata { - uploader_pub_key: vec![0_u8; 32], - nonce: vec![0_u8; 24], - }, - payload: vec![0_u8; 0], - }; - - let req = test::TestRequest::post() - .uri("/auth/tokens") - .set_json(&req_body) - .to_request(); - - let resp = test::call_service(&app, req).await; - let status: http::StatusCode = resp.status(); - let body: Bytes = test::read_body(resp).await; - - assert!( - status.is_success(), - "status = {}, body = {:?}", - status, - body - ); - - // Check the returned execution token - - let actual: exec_token::models::ResponseBody = serde_json::from_slice(body.as_ref()).unwrap(); - - // TODO: Placeholder value matching get_exec_token in rtc_uenclave::rtc_enclave - let expected = exec_token::models::ResponseBody { - execution_token: vec![128; 9], - nonce: vec![7; 24], - }; - assert_eq!(expected, actual) -} - -#[test] -fn test_local_attestation_success() { - let auth_enclave = rtc_uenclave::RtcAuthEnclave::init(EnclaveConfig { - lib_path: "/root/rtc-data/rtc_auth_enclave/build/bin/enclave.signed.so".to_string(), - ..Default::default() - }) - .unwrap(); - - let data_enclave = rtc_uenclave::RtcDataEnclave::init(EnclaveConfig { - lib_path: "/root/rtc-data/rtc_data_enclave/build/bin/enclave.signed.so".to_string(), - ..Default::default() - }) - .unwrap(); - - let res = data_enclave.local_attestation(auth_enclave.geteid()); - assert_eq!(res, sgx_status_t::SGX_SUCCESS); - - // TODO: Integration test for message sending - // We should consider moving the integration tests for enclave interaction into rtc_uenclave - // since these tests does not need anything from the data_service -} diff --git a/rtc_data_service/tests/helpers/mod.rs b/rtc_data_service/tests/helpers/mod.rs new file mode 100644 index 00000000..a8978c3a --- /dev/null +++ b/rtc_data_service/tests/helpers/mod.rs @@ -0,0 +1,62 @@ +//! Shared test helpers + +mod types; + +use std::sync::Arc; + +use actix::Actor; +use actix_web::App; + +use rtc_uenclave::{EnclaveConfig, RtcAuthEnclave, RtcDataEnclave}; + +use rtc_data_service::auth_enclave_actor::AuthEnclaveActor; +use rtc_data_service::data_enclave_actor::DataEnclaveActor; +use rtc_data_service::data_upload::upload_file; +use rtc_data_service::exec_token::req_exec_token; +use rtc_data_service::handlers; + +/// Initialise a data enclave for testing. +pub(crate) fn init_auth_enclave() -> RtcAuthEnclave { + RtcAuthEnclave::init(EnclaveConfig { + lib_path: "/root/rtc-data/rtc_auth_enclave/build/bin/enclave.signed.so".to_string(), + ..Default::default() + }) + .unwrap() +} + +/// Initialise a data enclave for testing. +pub(crate) fn init_data_enclave() -> RtcDataEnclave { + RtcDataEnclave::init(EnclaveConfig { + lib_path: "/root/rtc-data/rtc_data_enclave/build/bin/enclave.signed.so".to_string(), + ..Default::default() + }) + .unwrap() +} + +/// Initialise an instance of our web API for testing. +/// +/// This should (roughly) mirror our `HttpServer` definition in `http_server::main`. +pub(crate) async fn init_rtc_service() -> impl types::WebService { + let app = App::new() + .data(init_auth_enclave_actor().start()) + .data(init_data_enclave_actor().start()) + .service(handlers::auth_enclave_attestation) + .service(handlers::data_enclave_attestation) + .service(upload_file) + .service(req_exec_token); + actix_web::test::init_service(app).await +} + +fn init_auth_enclave_actor() -> AuthEnclaveActor { + AuthEnclaveActor::new(Arc::new(EnclaveConfig { + lib_path: "/root/rtc-data/rtc_auth_enclave/build/bin/enclave.signed.so".to_string(), + ..Default::default() + })) +} + +fn init_data_enclave_actor() -> DataEnclaveActor { + DataEnclaveActor::new(Arc::new(EnclaveConfig { + lib_path: "/root/rtc-data/rtc_data_enclave/build/bin/enclave.signed.so".to_string(), + ..Default::default() + })) +} diff --git a/rtc_data_service/tests/helpers/types.rs b/rtc_data_service/tests/helpers/types.rs new file mode 100644 index 00000000..2a25b771 --- /dev/null +++ b/rtc_data_service/tests/helpers/types.rs @@ -0,0 +1,17 @@ +//! Helper type definitions + +use actix_http::body::Body; +use actix_http::error::Error; +use actix_http::Request; +use actix_web::dev::{Service, ServiceResponse}; + +/// Shorthand for the complicated [`Service`] type returned by [`actix_web::test::init_service`]. +/// +/// This uses the "trait aliasing" technique described here: +/// +pub(crate) trait WebService: + Service, Error = Error> +{ +} + +impl WebService for S where S: Service, Error = Error> {} diff --git a/rtc_data_service/tests/main.rs b/rtc_data_service/tests/main.rs new file mode 100644 index 00000000..5b4f4d1b --- /dev/null +++ b/rtc_data_service/tests/main.rs @@ -0,0 +1,6 @@ +//! Top-level test module + +mod helpers; + +mod ecalls; +mod web_api; diff --git a/rtc_data_service/tests/server.rs b/rtc_data_service/tests/server.rs deleted file mode 100644 index 8a66a32c..00000000 --- a/rtc_data_service/tests/server.rs +++ /dev/null @@ -1,52 +0,0 @@ -use actix::Actor; -use actix_web::{test, test::read_body, App}; -use insta; -use rtc_data_service::auth_enclave_actor::AuthEnclaveActor; -use rtc_data_service::data_enclave_actor::DataEnclaveActor; -use rtc_data_service::handlers::*; -use rtc_uenclave::EnclaveConfig; - -use std::sync::Arc; - -#[actix_rt::test] -async fn auth_service_attestation_ok() { - attestation_ok("/auth/attest").await; -} - -#[actix_rt::test] -async fn data_service_attestation_ok() { - attestation_ok("/data/attest").await; -} - -async fn attestation_ok(uri_path: &str) { - let mut app = test::init_service( - App::new() - .data( - AuthEnclaveActor::new(Arc::new(EnclaveConfig { - lib_path: "/root/rtc-data/rtc_auth_enclave/build/bin/enclave.signed.so" - .to_string(), - ..Default::default() - })) - .start(), - ) - .data( - DataEnclaveActor::new(Arc::new(EnclaveConfig { - lib_path: "/root/rtc-data/rtc_data_enclave/build/bin/enclave.signed.so" - .to_string(), - ..Default::default() - })) - .start(), - ) - .service(auth_enclave_attestation) - .service(data_enclave_attestation), - ) - .await; - - let req = test::TestRequest::get().uri(uri_path).to_request(); - let resp = test::call_service(&mut app, req).await; - - insta::assert_debug_snapshot!(resp); - - let body = read_body(resp).await; - insta::assert_debug_snapshot!(body); -} diff --git a/rtc_data_service/tests/web_api/attestation.rs b/rtc_data_service/tests/web_api/attestation.rs new file mode 100644 index 00000000..1702a98c --- /dev/null +++ b/rtc_data_service/tests/web_api/attestation.rs @@ -0,0 +1,25 @@ +use actix_web::test; + +use crate::helpers; + +#[actix_rt::test] +async fn auth_service_attestation_ok() { + attestation_ok("/auth/attest").await; +} + +#[actix_rt::test] +async fn data_service_attestation_ok() { + attestation_ok("/data/attest").await; +} + +async fn attestation_ok(uri_path: &str) { + let app = helpers::init_rtc_service().await; + + let req = test::TestRequest::get().uri(uri_path).to_request(); + let resp = test::call_service(&app, req).await; + + insta::assert_debug_snapshot!(resp); + + let body = test::read_body(resp).await; + insta::assert_debug_snapshot!(body); +} diff --git a/rtc_data_service/tests/data_upload.rs b/rtc_data_service/tests/web_api/data_upload.rs similarity index 70% rename from rtc_data_service/tests/data_upload.rs rename to rtc_data_service/tests/web_api/data_upload.rs index d08b7303..f348b4b2 100644 --- a/rtc_data_service/tests/data_upload.rs +++ b/rtc_data_service/tests/web_api/data_upload.rs @@ -1,18 +1,16 @@ //! Tests for [`rtc_data_service::data_upload`] -use actix::Actor; -use actix_web::{ - test::{self, read_body}, - App, -}; -use rtc_data_service::data_enclave_actor::*; -use rtc_data_service::data_upload::*; -use rtc_uenclave::EnclaveConfig; +use std::convert::TryInto; +use std::path::Path; + use sgx_types::sgx_target_info_t; -use sodalite; + +use actix_web::test; use uuid::Uuid; -use std::{convert::TryInto, path::Path, sync::Arc}; +use rtc_data_service::data_upload::models; + +use crate::helpers; // See rtc_tenclave/src/crypto.rs const CRYPTO_BOX_ZEROBYTES: usize = 32; @@ -21,28 +19,11 @@ const CRYPTO_BOX_BOXZEROBYTES: usize = 16; /// Upload some data, decrypt and check the result. #[actix_rt::test] async fn data_service_data_upload_ok() { - // TODO: Split this test into re-usable components - let mut app = test::init_service( - App::new() - .data( - DataEnclaveActor::new(Arc::new(EnclaveConfig { - lib_path: "/root/rtc-data/rtc_data_enclave/build/bin/enclave.signed.so" - .to_string(), - ..Default::default() - })) - .start(), - ) - .service(upload_file), - ) - .await; + let app = helpers::init_rtc_service().await; // TODO: Add a test that can run inside of the enclave and use the JWT token to get // the enclave key - let enclave = rtc_uenclave::RtcDataEnclave::init(EnclaveConfig { - lib_path: "/root/rtc-data/rtc_data_enclave/build/bin/enclave.signed.so".to_string(), - ..Default::default() - }) - .unwrap(); + let enclave = helpers::init_data_enclave(); let enclave_pubkey = enclave .create_report(&sgx_target_info_t::default()) @@ -80,11 +61,11 @@ async fn data_service_data_upload_ok() { .set_json(&req_body) .to_request(); - let resp = test::call_service(&mut app, req).await; + let resp = test::call_service(&app, req).await; assert!(resp.status().is_success()); - let body: models::ResponseBody = serde_json::from_slice(&read_body(resp).await).unwrap(); + let body: models::ResponseBody = serde_json::from_slice(&test::read_body(resp).await).unwrap(); // NOTE: re-add padding since sodalite supports the C-style nacl api let mut m = vec![0_u8; body.ciphertext.len() + CRYPTO_BOX_BOXZEROBYTES]; diff --git a/rtc_data_service/tests/web_api/exec_token.rs b/rtc_data_service/tests/web_api/exec_token.rs new file mode 100644 index 00000000..0a9acdc8 --- /dev/null +++ b/rtc_data_service/tests/web_api/exec_token.rs @@ -0,0 +1,51 @@ +//! Tests for [`rtc_data_service::exec_token`] + +use actix_web::web::Bytes; +use actix_web::{http, test}; + +use rtc_data_service::exec_token; + +use crate::helpers; + +#[actix_rt::test] +async fn data_service_exec_token_ok() { + let app = helpers::init_rtc_service().await; + + // Call the endpoint + + // TODO: Placeholder request data, for now. + let req_body = exec_token::models::RequestBody { + metadata: exec_token::models::Metadata { + uploader_pub_key: vec![0_u8; 32], + nonce: vec![0_u8; 24], + }, + payload: vec![0_u8; 0], + }; + + let req = test::TestRequest::post() + .uri("/auth/tokens") + .set_json(&req_body) + .to_request(); + + let resp = test::call_service(&app, req).await; + let status: http::StatusCode = resp.status(); + let body: Bytes = test::read_body(resp).await; + + assert!( + status.is_success(), + "status = {}, body = {:?}", + status, + body + ); + + // Check the returned execution token + + let actual: exec_token::models::ResponseBody = serde_json::from_slice(body.as_ref()).unwrap(); + + // TODO: Placeholder value matching get_exec_token in rtc_uenclave::rtc_enclave + let expected = exec_token::models::ResponseBody { + execution_token: vec![128; 9], + nonce: vec![7; 24], + }; + assert_eq!(expected, actual) +} diff --git a/rtc_data_service/tests/web_api/mod.rs b/rtc_data_service/tests/web_api/mod.rs new file mode 100644 index 00000000..55099b06 --- /dev/null +++ b/rtc_data_service/tests/web_api/mod.rs @@ -0,0 +1,5 @@ +//! Web API tests + +mod attestation; +mod data_upload; +mod exec_token; diff --git a/rtc_data_service/tests/snapshots/server__auth_service_attestation_ok-2.snap b/rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__auth_service_attestation_ok-2.snap similarity index 100% rename from rtc_data_service/tests/snapshots/server__auth_service_attestation_ok-2.snap rename to rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__auth_service_attestation_ok-2.snap diff --git a/rtc_data_service/tests/snapshots/server__auth_service_attestation_ok.snap b/rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__auth_service_attestation_ok.snap similarity index 100% rename from rtc_data_service/tests/snapshots/server__auth_service_attestation_ok.snap rename to rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__auth_service_attestation_ok.snap diff --git a/rtc_data_service/tests/snapshots/server__data_service_attestation_ok-2.snap b/rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__data_service_attestation_ok-2.snap similarity index 100% rename from rtc_data_service/tests/snapshots/server__data_service_attestation_ok-2.snap rename to rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__data_service_attestation_ok-2.snap diff --git a/rtc_data_service/tests/snapshots/server__data_service_attestation_ok.snap b/rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__data_service_attestation_ok.snap similarity index 100% rename from rtc_data_service/tests/snapshots/server__data_service_attestation_ok.snap rename to rtc_data_service/tests/web_api/snapshots/main__web_api__attestation__data_service_attestation_ok.snap