Skip to content

Commit 922e36b

Browse files
committed
api: Make API configuration modular
Move the API configuration to a dedicated file and make it modular. The API configuration is separated for each supported version. Currently only the latest API version (v2.2) is supported. This is a preparation to support multiple API versions. Signed-off-by: Anderson Toshiyuki Sasaki <[email protected]>
1 parent d26802f commit 922e36b

File tree

3 files changed

+229
-112
lines changed

3 files changed

+229
-112
lines changed

keylime-agent/src/api.rs

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
use crate::{
2+
agent_handler,
3+
common::{JsonWrapper, API_VERSION},
4+
config, errors_handler, keys_handler, notifications_handler,
5+
quotes_handler,
6+
};
7+
use actix_web::{http, web, HttpRequest, HttpResponse, Responder, Scope};
8+
use log::*;
9+
use thiserror::Error;
10+
11+
pub const SUPPORTED_API_VERSIONS: &[&str] = &[API_VERSION];
12+
13+
#[derive(Error, Debug, PartialEq)]
14+
pub enum APIError {
15+
#[error("API version \"{0}\" not supported")]
16+
UnsupportedVersion(String),
17+
}
18+
19+
/// Handles the default case for the API version scope
20+
async fn api_default(req: HttpRequest) -> impl Responder {
21+
let error;
22+
let response;
23+
let message;
24+
25+
match req.head().method {
26+
http::Method::GET => {
27+
error = 400;
28+
message =
29+
"Not Implemented: Use /agent, /keys, or /quotes interfaces";
30+
response = HttpResponse::BadRequest()
31+
.json(JsonWrapper::error(error, message));
32+
}
33+
http::Method::POST => {
34+
error = 400;
35+
message =
36+
"Not Implemented: Use /keys or /notifications interfaces";
37+
response = HttpResponse::BadRequest()
38+
.json(JsonWrapper::error(error, message));
39+
}
40+
_ => {
41+
error = 405;
42+
message = "Method is not supported";
43+
response = HttpResponse::MethodNotAllowed()
44+
.insert_header(http::header::Allow(vec![
45+
http::Method::GET,
46+
http::Method::POST,
47+
]))
48+
.json(JsonWrapper::error(error, message));
49+
}
50+
};
51+
52+
warn!(
53+
"{} returning {} response. {}",
54+
req.head().method,
55+
error,
56+
message
57+
);
58+
59+
response
60+
}
61+
62+
/// Configure the endpoints supported by API version 2.1
63+
///
64+
/// Version 2.1 is the base API version
65+
fn configure_api_v2_1(cfg: &mut web::ServiceConfig) {
66+
_ = cfg
67+
.service(
68+
web::scope("/keys")
69+
.configure(keys_handler::configure_keys_endpoints),
70+
)
71+
.service(web::scope("/notifications").configure(
72+
notifications_handler::configure_notifications_endpoints,
73+
))
74+
.service(
75+
web::scope("/quotes")
76+
.configure(quotes_handler::configure_quotes_endpoints),
77+
)
78+
.default_service(web::to(api_default))
79+
}
80+
81+
/// Configure the endpoints supported by API version 2.2
82+
///
83+
/// The version 2.2 added the /agent/info endpoint
84+
fn configure_api_v2_2(cfg: &mut web::ServiceConfig) {
85+
// Configure the endpoints shared with version 2.1
86+
configure_api_v2_1(cfg);
87+
88+
// Configure added endpoints
89+
_ = cfg.service(
90+
web::scope("/agent")
91+
.configure(agent_handler::configure_agent_endpoints),
92+
)
93+
}
94+
95+
/// Get a scope configured for the given API version
96+
pub(crate) fn get_api_scope(version: &str) -> Result<Scope, APIError> {
97+
match version {
98+
"v2.1" => Ok(web::scope(version).configure(configure_api_v2_1)),
99+
"v2.2" => Ok(web::scope(version).configure(configure_api_v2_2)),
100+
_ => Err(APIError::UnsupportedVersion(version.into())),
101+
}
102+
}
103+
104+
#[cfg(test)]
105+
mod tests {
106+
use super::*;
107+
use actix_web::{test, web, App};
108+
use serde_json::{json, Value};
109+
110+
#[actix_rt::test]
111+
async fn test_configure_api() {
112+
// Test that invalid version results in error
113+
let result = get_api_scope("invalid");
114+
assert!(result.is_err());
115+
if let Err(e) = result {
116+
assert_eq!(e, APIError::UnsupportedVersion("invalid".into()));
117+
}
118+
119+
// Test that a valid version is successful
120+
let version = SUPPORTED_API_VERSIONS.last().unwrap(); //#[allow_ci]
121+
let result = get_api_scope(version);
122+
assert!(result.is_ok());
123+
let scope = result.unwrap(); //#[allow_ci]
124+
}
125+
126+
#[actix_rt::test]
127+
async fn test_api_default() {
128+
let mut app = test::init_service(
129+
App::new().service(web::resource("/").to(api_default)),
130+
)
131+
.await;
132+
133+
let req = test::TestRequest::get().uri("/").to_request();
134+
135+
let resp = test::call_service(&app, req).await;
136+
assert!(resp.status().is_client_error());
137+
138+
let result: JsonWrapper<Value> = test::read_body_json(resp).await;
139+
140+
assert_eq!(result.results, json!({}));
141+
assert_eq!(result.code, 400);
142+
143+
let req = test::TestRequest::post()
144+
.uri("/")
145+
.data("some data")
146+
.to_request();
147+
148+
let resp = test::call_service(&app, req).await;
149+
assert!(resp.status().is_client_error());
150+
151+
let result: JsonWrapper<Value> = test::read_body_json(resp).await;
152+
153+
assert_eq!(result.results, json!({}));
154+
assert_eq!(result.code, 400);
155+
156+
let req = test::TestRequest::delete().uri("/").to_request();
157+
158+
let resp = test::call_service(&app, req).await;
159+
assert!(resp.status().is_client_error());
160+
161+
let headers = resp.headers();
162+
163+
assert!(headers.contains_key("allow"));
164+
assert_eq!(
165+
headers.get("allow").unwrap().to_str().unwrap(), //#[allow_ci]
166+
"GET, POST"
167+
);
168+
169+
let result: JsonWrapper<Value> = test::read_body_json(resp).await;
170+
171+
assert_eq!(result.results, json!({}));
172+
assert_eq!(result.code, 405);
173+
}
174+
}

keylime-agent/src/errors_handler.rs

-47
Original file line numberDiff line numberDiff line change
@@ -54,48 +54,6 @@ pub(crate) async fn app_default(req: HttpRequest) -> impl Responder {
5454
response
5555
}
5656

57-
pub(crate) async fn api_default(req: HttpRequest) -> impl Responder {
58-
let error;
59-
let response;
60-
let message;
61-
62-
match req.head().method {
63-
http::Method::GET => {
64-
error = 400;
65-
message =
66-
"Not Implemented: Use /agent, /keys, or /quotes interfaces";
67-
response = HttpResponse::BadRequest()
68-
.json(JsonWrapper::error(error, message));
69-
}
70-
http::Method::POST => {
71-
error = 400;
72-
message =
73-
"Not Implemented: Use /keys or /notifications interfaces";
74-
response = HttpResponse::BadRequest()
75-
.json(JsonWrapper::error(error, message));
76-
}
77-
_ => {
78-
error = 405;
79-
message = "Method is not supported";
80-
response = HttpResponse::MethodNotAllowed()
81-
.insert_header(http::header::Allow(vec![
82-
http::Method::GET,
83-
http::Method::POST,
84-
]))
85-
.json(JsonWrapper::error(error, message));
86-
}
87-
};
88-
89-
warn!(
90-
"{} returning {} response. {}",
91-
req.head().method,
92-
error,
93-
message
94-
);
95-
96-
response
97-
}
98-
9957
pub(crate) async fn version_not_supported(
10058
req: HttpRequest,
10159
version: web::Path<APIVersion>,
@@ -219,11 +177,6 @@ mod tests {
219177
test_default(web::resource("/").to(app_default), "GET, POST").await
220178
}
221179

222-
#[actix_rt::test]
223-
async fn test_api_default() {
224-
test_default(web::resource("/").to(api_default), "GET, POST").await
225-
}
226-
227180
#[derive(Serialize, Deserialize)]
228181
struct DummyQuery {
229182
param: String,

keylime-agent/src/main.rs

+55-65
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#![allow(unused, missing_docs)]
3333

3434
mod agent_handler;
35+
mod api;
3536
mod common;
3637
mod config;
3738
mod error;
@@ -863,71 +864,60 @@ async fn main() -> Result<()> {
863864
secure_mount: PathBuf::from(&mount),
864865
});
865866

866-
let actix_server =
867-
HttpServer::new(move || {
868-
App::new()
869-
.wrap(middleware::ErrorHandlers::new().handler(
870-
http::StatusCode::NOT_FOUND,
871-
errors_handler::wrap_404,
872-
))
873-
.wrap(middleware::Logger::new(
874-
"%r from %a result %s (took %D ms)",
875-
))
876-
.wrap_fn(|req, srv| {
877-
info!(
878-
"{} invoked from {:?} with uri {}",
879-
req.head().method,
880-
req.connection_info().peer_addr().unwrap(), //#[allow_ci]
881-
req.uri()
882-
);
883-
srv.call(req)
884-
})
885-
.app_data(quotedata.clone())
886-
.app_data(
887-
web::JsonConfig::default()
888-
.error_handler(errors_handler::json_parser_error),
889-
)
890-
.app_data(
891-
web::QueryConfig::default()
892-
.error_handler(errors_handler::query_parser_error),
893-
)
894-
.app_data(
895-
web::PathConfig::default()
896-
.error_handler(errors_handler::path_parser_error),
897-
)
898-
.service(
899-
web::scope(&format!("/{API_VERSION}"))
900-
.service(web::scope("/agent").configure(
901-
agent_handler::configure_agent_endpoints,
902-
))
903-
.service(web::scope("/keys").configure(
904-
keys_handler::configure_keys_endpoints,
905-
))
906-
.service(
907-
web::scope("/notifications").configure(
908-
notifications_handler::configure_notifications_endpoints,
909-
))
910-
.service(web::scope("/quotes").configure(
911-
quotes_handler::configure_quotes_endpoints,
912-
))
913-
.default_service(web::to(
914-
errors_handler::api_default,
915-
)),
916-
)
917-
.service(
918-
web::resource("/version")
919-
.route(web::get().to(version_handler::version)),
920-
)
921-
.service(
922-
web::resource(r"/v{major:\d+}.{minor:\d+}{tail}*")
923-
.to(errors_handler::version_not_supported),
924-
)
925-
.default_service(web::to(errors_handler::app_default))
926-
})
927-
// Disable default signal handlers. See:
928-
// https://github.com/actix/actix-web/issues/2739
929-
// for details.
930-
.disable_signals();
867+
let actix_server = HttpServer::new(move || {
868+
let mut app = App::new()
869+
.wrap(middleware::ErrorHandlers::new().handler(
870+
http::StatusCode::NOT_FOUND,
871+
errors_handler::wrap_404,
872+
))
873+
.wrap(middleware::Logger::new(
874+
"%r from %a result %s (took %D ms)",
875+
))
876+
.wrap_fn(|req, srv| {
877+
info!(
878+
"{} invoked from {:?} with uri {}",
879+
req.head().method,
880+
req.connection_info().peer_addr().unwrap(), //#[allow_ci]
881+
req.uri()
882+
);
883+
srv.call(req)
884+
})
885+
.app_data(quotedata.clone())
886+
.app_data(
887+
web::JsonConfig::default()
888+
.error_handler(errors_handler::json_parser_error),
889+
)
890+
.app_data(
891+
web::QueryConfig::default()
892+
.error_handler(errors_handler::query_parser_error),
893+
)
894+
.app_data(
895+
web::PathConfig::default()
896+
.error_handler(errors_handler::path_parser_error),
897+
);
898+
899+
let enabled_api_versions = api::SUPPORTED_API_VERSIONS;
900+
901+
for version in enabled_api_versions {
902+
// This should never fail, thus unwrap should never panic
903+
let scope = api::get_api_scope(version).unwrap(); //#[allow_ci]
904+
app = app.service(scope);
905+
}
906+
907+
app.service(
908+
web::resource("/version")
909+
.route(web::get().to(version_handler::version)),
910+
)
911+
.service(
912+
web::resource(r"/v{major:\d+}.{minor:\d+}{tail}*")
913+
.to(errors_handler::version_not_supported),
914+
)
915+
.default_service(web::to(errors_handler::app_default))
916+
})
917+
// Disable default signal handlers. See:
918+
// https://github.com/actix/actix-web/issues/2739
919+
// for details.
920+
.disable_signals();
931921

932922
let server;
933923

0 commit comments

Comments
 (0)