Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the API configuration modular #861

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions keylime-agent/src/agent_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -19,7 +19,7 @@

// 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<QuoteData>,
) -> impl Responder {
Expand All @@ -38,12 +38,52 @@
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;

Check warning on line 46 in keylime-agent/src/agent_handler.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/agent_handler.rs#L43-L46

Added lines #L43 - L46 were not covered by tests
match req.head().method {
http::Method::GET => {

Check warning on line 48 in keylime-agent/src/agent_handler.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/agent_handler.rs#L48

Added line #L48 was not covered by tests
error = 400;
message = "URI not supported, only /info is supported for GET in /agent interface";
response = HttpResponse::BadRequest()
.json(JsonWrapper::error(error, message));
}
_ => {

Check warning on line 54 in keylime-agent/src/agent_handler.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/agent_handler.rs#L53-L54

Added lines #L53 - L54 were not covered by tests
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));
}

Check warning on line 60 in keylime-agent/src/agent_handler.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/agent_handler.rs#L60

Added line #L60 was not covered by tests
};

warn!(
"{} returning {} response. {}",
req.head().method,

Check warning on line 65 in keylime-agent/src/agent_handler.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/agent_handler.rs#L64-L65

Added lines #L64 - L65 were not covered by tests
error,
message
);

response
}

Check warning on line 71 in keylime-agent/src/agent_handler.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/agent_handler.rs#L71

Added line #L71 was not covered by tests

/// 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() {
Expand Down Expand Up @@ -73,4 +113,37 @@
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<Value> = 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<Value> = test::read_body_json(resp).await;

assert_eq!(result.results, json!({}));
assert_eq!(result.code, 405);
}
}
174 changes: 174 additions & 0 deletions keylime-agent/src/api.rs
Original file line number Diff line number Diff line change
@@ -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)]

Check warning on line 13 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L13

Added line #L13 was not covered by tests
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;

Check warning on line 24 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L21-L24

Added lines #L21 - L24 were not covered by tests
match req.head().method {
http::Method::GET => {

Check warning on line 26 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L26

Added line #L26 was not covered by tests
error = 400;
message =
"Not Implemented: Use /agent, /keys, or /quotes interfaces";

Check warning on line 29 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L29

Added line #L29 was not covered by tests
response = HttpResponse::BadRequest()
.json(JsonWrapper::error(error, message));
}
http::Method::POST => {

Check warning on line 33 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L32-L33

Added lines #L32 - L33 were not covered by tests
error = 400;
message =
"Not Implemented: Use /keys or /notifications interfaces";

Check warning on line 36 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L36

Added line #L36 was not covered by tests
response = HttpResponse::BadRequest()
.json(JsonWrapper::error(error, message));
}
_ => {

Check warning on line 40 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L39-L40

Added lines #L39 - L40 were not covered by tests
error = 405;
message = "Method is not supported";
response = HttpResponse::MethodNotAllowed()
.insert_header(http::header::Allow(vec![
http::Method::GET,
http::Method::POST,
]))

Check warning on line 47 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L45-L47

Added lines #L45 - L47 were not covered by tests
.json(JsonWrapper::error(error, message));
}

Check warning on line 49 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L49

Added line #L49 was not covered by tests
};

warn!(
"{} returning {} response. {}",
req.head().method,

Check warning on line 54 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L52-L54

Added lines #L52 - L54 were not covered by tests
error,
message
);

response
}

Check warning on line 60 in keylime-agent/src/api.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/api.rs#L60

Added line #L60 was not covered by tests

/// 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<Scope, APIError> {
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<Value> = 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<Value> = 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<Value> = test::read_body_json(resp).await;

assert_eq!(result.results, json!({}));
assert_eq!(result.code, 405);
}
}
Loading