diff --git a/keylime-agent/src/agent_handler.rs b/keylime-agent/src/agent_handler.rs index 11d02186..d75f3fa9 100644 --- a/keylime-agent/src/agent_handler.rs +++ b/keylime-agent/src/agent_handler.rs @@ -3,7 +3,7 @@ use crate::common::JsonWrapper; use crate::{tpm, Error as KeylimeError, QuoteData}; -use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use actix_web::{http, web, HttpRequest, HttpResponse, Responder}; use base64::{engine::general_purpose, Engine as _}; use log::*; use serde::{Deserialize, Serialize}; @@ -19,7 +19,7 @@ pub(crate) struct AgentInfo { // This is an Info request which gets some information about this keylime agent // It should return a AgentInfo object as JSON -pub async fn info( +async fn info( req: HttpRequest, data: web::Data, ) -> impl Responder { @@ -38,12 +38,52 @@ pub async fn info( HttpResponse::Ok().json(response) } +/// Configure the endpoints for the /agent scope +async fn agent_default(req: HttpRequest) -> impl Responder { + let error; + let response; + let message; + + match req.head().method { + http::Method::GET => { + error = 400; + message = "URI not supported, only /info is supported for GET in /agent interface"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + _ => { + error = 405; + message = "Method is not supported in /agent interface"; + response = HttpResponse::MethodNotAllowed() + .insert_header(http::header::Allow(vec![http::Method::GET])) + .json(JsonWrapper::error(error, message)); + } + }; + + warn!( + "{} returning {} response. {}", + req.head().method, + error, + message + ); + + response +} + +/// Configure the endpoints for the /agents scope +pub(crate) fn configure_agent_endpoints(cfg: &mut web::ServiceConfig) { + _ = cfg + .service(web::resource("/info").route(web::get().to(info))) + .default_service(web::to(agent_default)); +} + #[cfg(test)] #[cfg(feature = "testing")] mod tests { use super::*; use crate::common::API_VERSION; use actix_web::{test, web, App}; + use serde_json::{json, Value}; #[actix_rt::test] async fn test_agent_info() { @@ -73,4 +113,37 @@ mod tests { assert_eq!(result.results.tpm_enc_alg.as_str(), "rsa"); assert_eq!(result.results.tpm_sign_alg.as_str(), "rsassa"); } + + #[actix_rt::test] + async fn test_agents_default() { + let mut app = test::init_service( + App::new().service(web::resource("/").to(agent_default)), + ) + .await; + + let req = test::TestRequest::get().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 400); + + let req = test::TestRequest::delete().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let headers = resp.headers(); + + assert!(headers.contains_key("allow")); + assert_eq!(headers.get("allow").unwrap().to_str().unwrap(), "GET"); //#[allow_ci] + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 405); + } } diff --git a/keylime-agent/src/api.rs b/keylime-agent/src/api.rs new file mode 100644 index 00000000..9e0eb402 --- /dev/null +++ b/keylime-agent/src/api.rs @@ -0,0 +1,174 @@ +use crate::{ + agent_handler, + common::{JsonWrapper, API_VERSION}, + config, errors_handler, keys_handler, notifications_handler, + quotes_handler, +}; +use actix_web::{http, web, HttpRequest, HttpResponse, Responder, Scope}; +use log::*; +use thiserror::Error; + +pub const SUPPORTED_API_VERSIONS: &[&str] = &[API_VERSION]; + +#[derive(Error, Debug, PartialEq)] +pub enum APIError { + #[error("API version \"{0}\" not supported")] + UnsupportedVersion(String), +} + +/// Handles the default case for the API version scope +async fn api_default(req: HttpRequest) -> impl Responder { + let error; + let response; + let message; + + match req.head().method { + http::Method::GET => { + error = 400; + message = + "Not Implemented: Use /agent, /keys, or /quotes interfaces"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + http::Method::POST => { + error = 400; + message = + "Not Implemented: Use /keys or /notifications interfaces"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + _ => { + error = 405; + message = "Method is not supported"; + response = HttpResponse::MethodNotAllowed() + .insert_header(http::header::Allow(vec![ + http::Method::GET, + http::Method::POST, + ])) + .json(JsonWrapper::error(error, message)); + } + }; + + warn!( + "{} returning {} response. {}", + req.head().method, + error, + message + ); + + response +} + +/// Configure the endpoints supported by API version 2.1 +/// +/// Version 2.1 is the base API version +fn configure_api_v2_1(cfg: &mut web::ServiceConfig) { + _ = cfg + .service( + web::scope("/keys") + .configure(keys_handler::configure_keys_endpoints), + ) + .service(web::scope("/notifications").configure( + notifications_handler::configure_notifications_endpoints, + )) + .service( + web::scope("/quotes") + .configure(quotes_handler::configure_quotes_endpoints), + ) + .default_service(web::to(api_default)) +} + +/// Configure the endpoints supported by API version 2.2 +/// +/// The version 2.2 added the /agent/info endpoint +fn configure_api_v2_2(cfg: &mut web::ServiceConfig) { + // Configure the endpoints shared with version 2.1 + configure_api_v2_1(cfg); + + // Configure added endpoints + _ = cfg.service( + web::scope("/agent") + .configure(agent_handler::configure_agent_endpoints), + ) +} + +/// Get a scope configured for the given API version +pub(crate) fn get_api_scope(version: &str) -> Result { + match version { + "v2.1" => Ok(web::scope(version).configure(configure_api_v2_1)), + "v2.2" => Ok(web::scope(version).configure(configure_api_v2_2)), + _ => Err(APIError::UnsupportedVersion(version.into())), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, web, App}; + use serde_json::{json, Value}; + + #[actix_rt::test] + async fn test_configure_api() { + // Test that invalid version results in error + let result = get_api_scope("invalid"); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e, APIError::UnsupportedVersion("invalid".into())); + } + + // Test that a valid version is successful + let version = SUPPORTED_API_VERSIONS.last().unwrap(); //#[allow_ci] + let result = get_api_scope(version); + assert!(result.is_ok()); + let scope = result.unwrap(); //#[allow_ci] + } + + #[actix_rt::test] + async fn test_api_default() { + let mut app = test::init_service( + App::new().service(web::resource("/").to(api_default)), + ) + .await; + + let req = test::TestRequest::get().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 400); + + let req = test::TestRequest::post() + .uri("/") + .data("some data") + .to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 400); + + let req = test::TestRequest::delete().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let headers = resp.headers(); + + assert!(headers.contains_key("allow")); + assert_eq!( + headers.get("allow").unwrap().to_str().unwrap(), //#[allow_ci] + "GET, POST" + ); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 405); + } +} diff --git a/keylime-agent/src/errors_handler.rs b/keylime-agent/src/errors_handler.rs index 4c01979d..0d218af1 100644 --- a/keylime-agent/src/errors_handler.rs +++ b/keylime-agent/src/errors_handler.rs @@ -54,183 +54,6 @@ pub(crate) async fn app_default(req: HttpRequest) -> impl Responder { response } -pub(crate) async fn api_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { - http::Method::GET => { - error = 400; - message = - "Not Implemented: Use /agent, /keys, or /quotes interfaces"; - response = HttpResponse::BadRequest() - .json(JsonWrapper::error(error, message)); - } - http::Method::POST => { - error = 400; - message = - "Not Implemented: Use /keys or /notifications interfaces"; - response = HttpResponse::BadRequest() - .json(JsonWrapper::error(error, message)); - } - _ => { - error = 405; - message = "Method is not supported"; - response = HttpResponse::MethodNotAllowed() - .insert_header(http::header::Allow(vec![ - http::Method::GET, - http::Method::POST, - ])) - .json(JsonWrapper::error(error, message)); - } - }; - - warn!( - "{} returning {} response. {}", - req.head().method, - error, - message - ); - - response -} - -pub(crate) async fn keys_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { - http::Method::GET => { - error = 400; - message = "URI not supported, only /pubkey and /verify are supported for GET in /keys interface"; - response = HttpResponse::BadRequest() - .json(JsonWrapper::error(error, message)); - } - http::Method::POST => { - error = 400; - message = "URI not supported, only /ukey and /vkey are supported for POST in /keys interface"; - response = HttpResponse::BadRequest() - .json(JsonWrapper::error(error, message)); - } - _ => { - error = 405; - message = "Method is not supported in /keys interface"; - response = HttpResponse::MethodNotAllowed() - .insert_header(http::header::Allow(vec![ - http::Method::GET, - http::Method::POST, - ])) - .json(JsonWrapper::error(error, message)); - } - }; - - warn!( - "{} returning {} response. {}", - req.head().method, - error, - message - ); - - response -} - -pub(crate) async fn quotes_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { - http::Method::GET => { - error = 400; - message = "URI not supported, only /identity and /integrity are supported for GET in /quotes/ interface"; - response = HttpResponse::BadRequest() - .json(JsonWrapper::error(error, message)); - } - _ => { - error = 405; - message = "Method is not supported in /quotes/ interface"; - response = HttpResponse::MethodNotAllowed() - .insert_header(http::header::Allow(vec![http::Method::GET])) - .json(JsonWrapper::error(error, message)); - } - }; - - warn!( - "{} returning {} response. {}", - req.head().method, - error, - message - ); - - response -} - -pub(crate) async fn agent_default(req: HttpRequest) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { - http::Method::GET => { - error = 400; - message = "URI not supported, only /info is supported for GET in /agent interface"; - response = HttpResponse::BadRequest() - .json(JsonWrapper::error(error, message)); - } - _ => { - error = 405; - message = "Method is not supported in /agent interface"; - response = HttpResponse::MethodNotAllowed() - .insert_header(http::header::Allow(vec![http::Method::GET])) - .json(JsonWrapper::error(error, message)); - } - }; - - warn!( - "{} returning {} response. {}", - req.head().method, - error, - message - ); - - response -} - -pub(crate) async fn notifications_default( - req: HttpRequest, -) -> impl Responder { - let error; - let response; - let message; - - match req.head().method { - http::Method::POST => { - error = 400; - message = "URI not supported, only /revocation is supported for POST in /notifications/ interface"; - response = HttpResponse::BadRequest() - .json(JsonWrapper::error(error, message)); - } - _ => { - error = 405; - message = "Method is not supported in /notifications/ interface"; - response = HttpResponse::MethodNotAllowed() - .insert_header(http::header::Allow(vec![http::Method::POST])) - .json(JsonWrapper::error(error, message)); - } - }; - - warn!( - "{} returning {} response. {}", - req.head().method, - error, - message - ); - - response -} - pub(crate) async fn version_not_supported( req: HttpRequest, version: web::Path, @@ -354,32 +177,6 @@ mod tests { test_default(web::resource("/").to(app_default), "GET, POST").await } - #[actix_rt::test] - async fn test_api_default() { - test_default(web::resource("/").to(api_default), "GET, POST").await - } - - #[actix_rt::test] - async fn test_keys_default() { - test_default(web::resource("/").to(keys_default), "GET, POST").await - } - - #[actix_rt::test] - async fn test_quotes_default() { - test_default(web::resource("/").to(quotes_default), "GET").await - } - - #[actix_rt::test] - async fn test_notifications_default() { - test_default(web::resource("/").to(notifications_default), "POST") - .await - } - - #[actix_rt::test] - async fn test_agent_default() { - test_default(web::resource("/").to(agent_default), "GET").await - } - #[derive(Serialize, Deserialize)] struct DummyQuery { param: String, diff --git a/keylime-agent/src/keys_handler.rs b/keylime-agent/src/keys_handler.rs index 3407deeb..da241616 100644 --- a/keylime-agent/src/keys_handler.rs +++ b/keylime-agent/src/keys_handler.rs @@ -11,7 +11,7 @@ use crate::{ payloads::{Payload, PayloadMessage}, Error, QuoteData, Result, }; -use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use actix_web::{http, web, HttpRequest, HttpResponse, Responder}; use base64::{engine::general_purpose, Engine as _}; use log::*; use serde::{Deserialize, Serialize}; @@ -130,7 +130,7 @@ fn try_combine_keys( None } -pub(crate) async fn u_key( +async fn u_key( body: web::Json, req: HttpRequest, quote_data: web::Data, @@ -244,7 +244,7 @@ pub(crate) async fn u_key( HttpResponse::Ok().json(JsonWrapper::success(())) } -pub(crate) async fn v_key( +async fn v_key( body: web::Json, req: HttpRequest, quote_data: web::Data, @@ -315,7 +315,7 @@ pub(crate) async fn v_key( HttpResponse::Ok().json(JsonWrapper::success(())) } -pub(crate) async fn pubkey( +async fn pubkey( req: HttpRequest, data: web::Data, ) -> impl Responder { @@ -364,7 +364,7 @@ async fn get_symm_key( } } -pub(crate) async fn verify( +async fn verify( param: web::Query, req: HttpRequest, data: web::Data, @@ -544,6 +544,57 @@ pub(crate) async fn worker( Ok(()) } +/// Handles the default case for the /keys scope +async fn keys_default(req: HttpRequest) -> impl Responder { + let error; + let response; + let message; + + match req.head().method { + http::Method::GET => { + error = 400; + message = "URI not supported, only /pubkey and /verify are supported for GET in /keys interface"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + http::Method::POST => { + error = 400; + message = "URI not supported, only /ukey and /vkey are supported for POST in /keys interface"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + _ => { + error = 405; + message = "Method is not supported in /keys interface"; + response = HttpResponse::MethodNotAllowed() + .insert_header(http::header::Allow(vec![ + http::Method::GET, + http::Method::POST, + ])) + .json(JsonWrapper::error(error, message)); + } + }; + + warn!( + "{} returning {} response. {}", + req.head().method, + error, + message + ); + + response +} + +/// Configure the endpoints for the /keys scope +pub(crate) fn configure_keys_endpoints(cfg: &mut web::ServiceConfig) { + _ = cfg + .service(web::resource("/pubkey").route(web::get().to(pubkey))) + .service(web::resource("/ukey").route(web::post().to(u_key))) + .service(web::resource("/verify").route(web::get().to(verify))) + .service(web::resource("/vkey").route(web::post().to(v_key))) + .default_service(web::to(keys_default)); +} + #[cfg(test)] mod tests { use super::*; @@ -567,6 +618,7 @@ mod tests { rsa::Padding, sign::Signer, }; + use serde_json::{json, Value}; use std::{ env, fs, path::{Path, PathBuf}, @@ -771,6 +823,55 @@ mod tests { } } + #[actix_rt::test] + async fn test_keys_default() { + let mut app = test::init_service( + App::new().service(web::resource("/").to(keys_default)), + ) + .await; + + let req = test::TestRequest::get().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 400); + + let req = test::TestRequest::post() + .uri("/") + .data("some data") + .to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 400); + + let req = test::TestRequest::delete().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let headers = resp.headers(); + + assert!(headers.contains_key("allow")); + assert_eq!( + headers.get("allow").unwrap().to_str().unwrap(), //#[allow_ci] + "GET, POST" + ); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 405); + } + #[cfg(feature = "testing")] async fn test_u_or_v_key(key_len: usize, payload: Option<&[u8]>) { let test_config = KeylimeConfig::default(); diff --git a/keylime-agent/src/main.rs b/keylime-agent/src/main.rs index a5137a7b..e331807b 100644 --- a/keylime-agent/src/main.rs +++ b/keylime-agent/src/main.rs @@ -32,6 +32,7 @@ #![allow(unused, missing_docs)] mod agent_handler; +mod api; mod common; mod config; mod error; @@ -863,108 +864,60 @@ async fn main() -> Result<()> { secure_mount: PathBuf::from(&mount), }); - let actix_server = - HttpServer::new(move || { - App::new() - .wrap(middleware::ErrorHandlers::new().handler( - http::StatusCode::NOT_FOUND, - errors_handler::wrap_404, - )) - .wrap(middleware::Logger::new( - "%r from %a result %s (took %D ms)", - )) - .wrap_fn(|req, srv| { - info!( - "{} invoked from {:?} with uri {}", - req.head().method, - req.connection_info().peer_addr().unwrap(), //#[allow_ci] - req.uri() - ); - srv.call(req) - }) - .app_data(quotedata.clone()) - .app_data( - web::JsonConfig::default() - .error_handler(errors_handler::json_parser_error), - ) - .app_data( - web::QueryConfig::default() - .error_handler(errors_handler::query_parser_error), - ) - .app_data( - web::PathConfig::default() - .error_handler(errors_handler::path_parser_error), - ) - .service( - web::scope(&format!("/{API_VERSION}")) - .service( - web::scope("/agent") - .service(web::resource("/info").route( - web::get().to(agent_handler::info), - )) - .default_service(web::to( - errors_handler::agent_default, - )), - ) - .service( - web::scope("/keys") - .service(web::resource("/pubkey").route( - web::get().to(keys_handler::pubkey), - )) - .service(web::resource("/ukey").route( - web::post().to(keys_handler::u_key), - )) - .service(web::resource("/verify").route( - web::get().to(keys_handler::verify), - )) - .service(web::resource("/vkey").route( - web::post().to(keys_handler::v_key), - )) - .default_service(web::to( - errors_handler::keys_default, - )), - ) - .service( - web::scope("/notifications") - .service(web::resource("/revocation").route( - web::post().to( - notifications_handler::revocation, - ), - )) - .default_service(web::to( - errors_handler::notifications_default, - )), - ) - .service( - web::scope("/quotes") - .service(web::resource("/identity").route( - web::get().to(quotes_handler::identity), - )) - .service(web::resource("/integrity").route( - web::get().to(quotes_handler::integrity), - )) - .default_service(web::to( - errors_handler::quotes_default, - )), - ) - .default_service(web::to( - errors_handler::api_default, - )), - ) - .service( - web::resource("/version") - .route(web::get().to(version_handler::version)), - ) - .service( - web::resource(r"/v{major:\d+}.{minor:\d+}{tail}*") - .to(errors_handler::version_not_supported), - ) - .default_service(web::to(errors_handler::app_default)) - }) - // Disable default signal handlers. See: - // https://github.com/actix/actix-web/issues/2739 - // for details. - .disable_signals(); + let actix_server = HttpServer::new(move || { + let mut app = App::new() + .wrap(middleware::ErrorHandlers::new().handler( + http::StatusCode::NOT_FOUND, + errors_handler::wrap_404, + )) + .wrap(middleware::Logger::new( + "%r from %a result %s (took %D ms)", + )) + .wrap_fn(|req, srv| { + info!( + "{} invoked from {:?} with uri {}", + req.head().method, + req.connection_info().peer_addr().unwrap(), //#[allow_ci] + req.uri() + ); + srv.call(req) + }) + .app_data(quotedata.clone()) + .app_data( + web::JsonConfig::default() + .error_handler(errors_handler::json_parser_error), + ) + .app_data( + web::QueryConfig::default() + .error_handler(errors_handler::query_parser_error), + ) + .app_data( + web::PathConfig::default() + .error_handler(errors_handler::path_parser_error), + ); + + let enabled_api_versions = api::SUPPORTED_API_VERSIONS; + + for version in enabled_api_versions { + // This should never fail, thus unwrap should never panic + let scope = api::get_api_scope(version).unwrap(); //#[allow_ci] + app = app.service(scope); + } + + app.service( + web::resource("/version") + .route(web::get().to(version_handler::version)), + ) + .service( + web::resource(r"/v{major:\d+}.{minor:\d+}{tail}*") + .to(errors_handler::version_not_supported), + ) + .default_service(web::to(errors_handler::app_default)) + }) + // Disable default signal handlers. See: + // https://github.com/actix/actix-web/issues/2739 + // for details. + .disable_signals(); let server; diff --git a/keylime-agent/src/notifications_handler.rs b/keylime-agent/src/notifications_handler.rs index 7e19ba87..0eeeae8f 100644 --- a/keylime-agent/src/notifications_handler.rs +++ b/keylime-agent/src/notifications_handler.rs @@ -6,13 +6,13 @@ use crate::{ revocation::{Revocation, RevocationMessage}, Error, QuoteData, Result, }; -use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use actix_web::{http, web, HttpRequest, HttpResponse, Responder}; use log::*; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; // This is Revocation request from the cloud verifier via REST API -pub(crate) async fn revocation( +async fn revocation( body: web::Json, req: HttpRequest, data: web::Data, @@ -35,16 +35,97 @@ pub(crate) async fn revocation( } } +async fn notifications_default(req: HttpRequest) -> impl Responder { + let error; + let response; + let message; + + match req.head().method { + http::Method::POST => { + error = 400; + message = "URI not supported, only /revocation is supported for POST in /notifications/ interface"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + _ => { + error = 405; + message = "Method is not supported in /notifications/ interface"; + response = HttpResponse::MethodNotAllowed() + .insert_header(http::header::Allow(vec![http::Method::POST])) + .json(JsonWrapper::error(error, message)); + } + }; + + warn!( + "{} returning {} response. {}", + req.head().method, + error, + message + ); + + response +} + +/// Configure the endpoints for the /notifications scope +pub(crate) fn configure_notifications_endpoints( + cfg: &mut web::ServiceConfig, +) { + _ = cfg + .service( + web::resource("/revocation").route(web::post().to(revocation)), + ) + .default_service(web::to(notifications_default)); +} + #[cfg(test)] mod tests { use super::*; use crate::common::API_VERSION; use actix_rt::Arbiter; use actix_web::{test, web, App}; - use serde_json::json; + use serde_json::{json, Value}; use std::{fs, path::Path}; use tokio::sync::mpsc; + #[actix_rt::test] + async fn test_notifications_default() { + let mut app = test::init_service( + App::new().service(web::resource("/").to(notifications_default)), + ) + .await; + + let req = test::TestRequest::post() + .uri("/") + .data("some data") + .to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 400); + + let req = test::TestRequest::delete().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let headers = resp.headers(); + + assert!(headers.contains_key("allow")); + assert_eq!( + headers.get("allow").unwrap().to_str().unwrap(), //#[allow_ci] + "POST" + ); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 405); + } + #[cfg(feature = "testing")] #[actix_rt::test] async fn test_revocation() { diff --git a/keylime-agent/src/quotes_handler.rs b/keylime-agent/src/quotes_handler.rs index 61a7fc25..ce72592e 100644 --- a/keylime-agent/src/quotes_handler.rs +++ b/keylime-agent/src/quotes_handler.rs @@ -5,7 +5,7 @@ use crate::common::JsonWrapper; use crate::crypto; use crate::serialization::serialize_maybe_base64; use crate::{tpm, Error as KeylimeError, QuoteData}; -use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use actix_web::{http, web, HttpRequest, HttpResponse, Responder}; use base64::{engine::general_purpose, Engine as _}; use log::*; use serde::{Deserialize, Serialize}; @@ -47,7 +47,7 @@ pub(crate) struct KeylimeQuote { // This is a Quote request from the tenant, which does not check // integrity measurement. It should return this data: // { QuoteAIK(nonce, 16:H(NK_pub)), NK_pub } -pub async fn identity( +async fn identity( req: HttpRequest, param: web::Query, data: web::Data, @@ -136,7 +136,7 @@ pub async fn identity( // by the mask. It should return this data: // { QuoteAIK(nonce, 16:H(NK_pub), xi:yi), NK_pub} // where xi:yi are additional PCRs to be included in the quote. -pub async fn integrity( +async fn integrity( req: HttpRequest, param: web::Query, data: web::Data, @@ -336,6 +336,46 @@ pub async fn integrity( HttpResponse::Ok().json(response) } +/// Handles the default case for the /quotes scope +async fn quotes_default(req: HttpRequest) -> impl Responder { + let error; + let response; + let message; + + match req.head().method { + http::Method::GET => { + error = 400; + message = "URI not supported, only /identity and /integrity are supported for GET in /quotes/ interface"; + response = HttpResponse::BadRequest() + .json(JsonWrapper::error(error, message)); + } + _ => { + error = 405; + message = "Method is not supported in /quotes/ interface"; + response = HttpResponse::MethodNotAllowed() + .insert_header(http::header::Allow(vec![http::Method::GET])) + .json(JsonWrapper::error(error, message)); + } + }; + + warn!( + "{} returning {} response. {}", + req.head().method, + error, + message + ); + + response +} + +/// Configure the endpoints for the /quotes scope +pub(crate) fn configure_quotes_endpoints(cfg: &mut web::ServiceConfig) { + _ = cfg + .service(web::resource("/identity").route(web::get().to(identity))) + .service(web::resource("/integrity").route(web::get().to(integrity))) + .default_service(web::to(quotes_default)); +} + #[cfg(feature = "testing")] #[cfg(test)] mod tests { @@ -343,6 +383,7 @@ mod tests { use crate::common::API_VERSION; use actix_web::{test, web, App}; use keylime::{crypto::testing::pkey_pub_from_pem, tpm}; + use serde_json::{json, Value}; #[actix_rt::test] async fn test_identity() { @@ -523,4 +564,37 @@ mod tests { assert!(result.results.ima_measurement_list.is_none()); assert!(result.results.ima_measurement_list_entry.is_none()); } + + #[actix_rt::test] + async fn test_keys_default() { + let mut app = test::init_service( + App::new().service(web::resource("/").to(quotes_default)), + ) + .await; + + let req = test::TestRequest::get().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 400); + + let req = test::TestRequest::delete().uri("/").to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_client_error()); + + let headers = resp.headers(); + + assert!(headers.contains_key("allow")); + assert_eq!(headers.get("allow").unwrap().to_str().unwrap(), "GET"); //#[allow_ci] + + let result: JsonWrapper = test::read_body_json(resp).await; + + assert_eq!(result.results, json!({})); + assert_eq!(result.code, 405); + } }