Skip to content

Commit

Permalink
Refactor auth integration tests to unit tests (Azure#1563)
Browse files Browse the repository at this point in the history
Refactors integration tests for authentication in edgelet-kube crate to be unit tests instead.
  • Loading branch information
dmolokanov authored Aug 13, 2019
1 parent 3899959 commit 0ae9a09
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 695 deletions.
70 changes: 69 additions & 1 deletion edgelet/edgelet-kube/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,21 @@ pub use settings::Settings;

#[cfg(test)]
mod tests {
use crate::settings::Settings;
use config::{Config, File, FileFormat};
use futures::future;
use hyper::service::Service;
use hyper::{Body, Request, Response, StatusCode};
use json_patch::merge;
use native_tls::TlsConnector;
use serde_json::{self, json, Value as JsonValue};
use typed_headers::{mime, ContentLength, ContentType, HeaderMapExt};
use url::Url;

use edgelet_test_utils::web::ResponseFuture;
use kube_client::{Client as KubeClient, Config as KubeConfig, Error, TokenSource};

use crate::settings::Settings;
use crate::KubeModuleRuntime;

pub const PROXY_TRUST_BUNDLE_CONFIG_MAP_NAME: &str = "device1-iotedged-proxy-trust-bundle";

Expand Down Expand Up @@ -80,4 +91,61 @@ mod tests {

config.try_into().unwrap()
}

#[derive(Clone)]
pub struct TestTokenSource;

impl TokenSource for TestTokenSource {
type Error = Error;

fn get(&self) -> kube_client::error::Result<Option<String>> {
Ok(None)
}
}

pub fn create_runtime<S: Service>(
settings: Settings,
service: S,
) -> KubeModuleRuntime<TestTokenSource, S> {
let client = KubeClient::with_client(get_config(), service);

KubeModuleRuntime::new(client, settings)
}

pub fn get_config() -> KubeConfig<TestTokenSource> {
KubeConfig::new(
Url::parse("https://localhost:443").unwrap(),
"/api".to_string(),
TestTokenSource,
TlsConnector::new().unwrap(),
)
}

pub fn response(
status_code: StatusCode,
response: impl Fn() -> String + Clone + Send + 'static,
) -> ResponseFuture {
let response = response();
let response_len = response.len();

let mut response = Response::new(response.into());
*response.status_mut() = status_code;
response
.headers_mut()
.typed_insert(&ContentLength(response_len as u64));
response
.headers_mut()
.typed_insert(&ContentType(mime::APPLICATION_JSON));

Box::new(future::ok(response)) as ResponseFuture
}

pub fn not_found_handler(_: Request<Body>) -> ResponseFuture {
let response = Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::default())
.unwrap();

Box::new(future::ok(response))
}
}
269 changes: 269 additions & 0 deletions edgelet/edgelet-kube/src/module/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,272 @@ where
None => Either::B(future::ok(AuthId::None)),
}
}

#[cfg(test)]
mod tests {
use futures::future;
use hyper::service::service_fn;
use hyper::{header, Body, Method, Request, Response, StatusCode};
use maplit::btreemap;
use serde_json::json;
use tokio::runtime::Runtime;
use typed_headers::{mime, ContentLength, ContentType, HeaderMapExt};

use edgelet_core::{AuthId, Authenticator};
use edgelet_test_utils::routes;
use edgelet_test_utils::web::{
make_req_dispatcher, HttpMethod, RequestHandler, RequestPath, ResponseFuture,
};

use crate::tests::{create_runtime, make_settings, not_found_handler, response};
use crate::ErrorKind;

#[test]
fn it_authenticates_with_none_when_no_auth_token_provided() {
let settings = make_settings(None);

let dispatch_table = routes!(
POST "/apis/authentication.k8s.io/v1/tokenreviews" => unauthenticated_token_review_handler()
);

let handler = make_req_dispatcher(dispatch_table, Box::new(not_found_handler));
let service = service_fn(handler);
let runtime = create_runtime(settings, service);
let req = Request::default();

let task = runtime.authenticate(&req);

let mut runtime = Runtime::new().unwrap();
let auth_id = runtime.block_on(task).unwrap();

assert_eq!(auth_id, AuthId::None)
}

#[test]
fn it_authenticates_with_none_when_invalid_auth_header_provided() {
let settings = make_settings(None);

let dispatch_table = routes!(
POST "/apis/authentication.k8s.io/v1/tokenreviews" => unauthenticated_token_review_handler()
);

let handler = make_req_dispatcher(dispatch_table, Box::new(not_found_handler));
let service = service_fn(handler);
let runtime = create_runtime(settings, service);
let mut req = Request::default();
req.headers_mut()
.insert(header::AUTHORIZATION, "BeErer token".parse().unwrap());

let task = runtime.authenticate(&req);

let mut runtime = Runtime::new().unwrap();
let auth_id = runtime.block_on(task).unwrap();

assert_eq!(auth_id, AuthId::None)
}

#[test]
fn it_authenticates_with_none_when_invalid_auth_token_provided() {
let settings = make_settings(None);

let dispatch_table = routes!(
POST "/apis/authentication.k8s.io/v1/tokenreviews" => unauthenticated_token_review_handler()
);

let handler = make_req_dispatcher(dispatch_table, Box::new(not_found_handler));
let service = service_fn(handler);
let runtime = create_runtime(settings, service);
let mut req = Request::default();
req.headers_mut().insert(
header::AUTHORIZATION,
"\u{3aa}\u{3a9}\u{3a4}".parse().unwrap(),
);

let task = runtime.authenticate(&req);

let mut runtime = Runtime::new().unwrap();
let err = runtime.block_on(task).unwrap_err();

assert_eq!(err.kind(), &ErrorKind::ModuleAuthenticationError);
}

#[test]
fn it_authenticates_with_none_when_unknown_auth_token_provided() {
let settings = make_settings(None);

let dispatch_table = routes!(
POST "/apis/authentication.k8s.io/v1/tokenreviews" => unauthenticated_token_review_handler()
);

let handler = make_req_dispatcher(dispatch_table, Box::new(not_found_handler));
let service = service_fn(handler);
let runtime = create_runtime(settings, service);
let mut req = Request::default();
req.headers_mut().insert(
header::AUTHORIZATION,
"Bearer token-unknown".parse().unwrap(),
);

let task = runtime.authenticate(&req);

let mut runtime = Runtime::new().unwrap();
let auth_id = runtime.block_on(task).unwrap();

assert_eq!(auth_id, AuthId::None)
}

#[test]
fn it_authenticates_with_none_when_module_auth_token_provided_but_sa_does_not_exists() {
let settings = make_settings(None);

let dispatch_table = routes!(
POST "/apis/authentication.k8s.io/v1/tokenreviews" => authenticated_token_review_handler(),
);

let handler = make_req_dispatcher(dispatch_table, Box::new(not_found_handler));
let service = service_fn(handler);
let runtime = create_runtime(settings, service);
let mut req = Request::default();
req.headers_mut()
.insert(header::AUTHORIZATION, "Bearer token".parse().unwrap());

let task = runtime.authenticate(&req);

let mut runtime = Runtime::new().unwrap();
let err = runtime.block_on(task).unwrap_err();

assert_eq!(err.kind(), &ErrorKind::KubeClient);
}

#[test]
fn it_authenticates_with_sa_name_when_sa_does_not_contain_original_name() {
let settings = make_settings(None);

let dispatch_table = routes!(
POST "/apis/authentication.k8s.io/v1/tokenreviews" => authenticated_token_review_handler(),
GET format!("/api/v1/namespaces/{}/serviceaccounts/edgeagent", settings.namespace()) => get_service_account_without_annotations_handler(),
);

let handler = make_req_dispatcher(dispatch_table, Box::new(not_found_handler));
let service = service_fn(handler);
let runtime = create_runtime(settings, service);
let mut req = Request::default();
req.headers_mut()
.insert(header::AUTHORIZATION, "Bearer token".parse().unwrap());

let task = runtime.authenticate(&req);

let mut runtime = Runtime::new().unwrap();
let auth_id = runtime.block_on(task).unwrap();

assert_eq!(auth_id, AuthId::Value("edgeagent".into()));
}

#[test]
fn it_authenticates_with_original_name_when_module_auth_token_provided() {
let settings = make_settings(None);

let dispatch_table = routes!(
POST "/apis/authentication.k8s.io/v1/tokenreviews" => authenticated_token_review_handler(),
GET format!("/api/v1/namespaces/{}/serviceaccounts/edgeagent", settings.namespace()) => get_service_account_with_annotations_handler(),
);

let handler = make_req_dispatcher(dispatch_table, Box::new(not_found_handler));
let service = service_fn(handler);
let runtime = create_runtime(settings, service);
let mut req = Request::default();
req.headers_mut()
.insert(header::AUTHORIZATION, "Bearer token".parse().unwrap());

let task = runtime.authenticate(&req);

let mut runtime = Runtime::new().unwrap();
let auth_id = runtime.block_on(task).unwrap();

assert_eq!(auth_id, AuthId::Value("$edgeAgent".into()));
}

fn authenticated_token_review_handler() -> impl Fn(Request<Body>) -> ResponseFuture + Clone {
make_token_review_handler(|| {
json!({
"kind": "TokenReview",
"spec": { "token": "token" },
"status": {
"authenticated": true,
"user": {
"username": "system:serviceaccount:my-namespace:edgeagent"
}
}}
)
.to_string()
})
}

fn unauthenticated_token_review_handler() -> impl Fn(Request<Body>) -> ResponseFuture + Clone {
make_token_review_handler(|| {
json!({
"kind": "TokenReview",
"spec": { "token": "token" },
"status": {
"authenticated": false,
}}
)
.to_string()
})
}

fn make_token_review_handler(
on_token_review: impl Fn() -> String + Clone + Send + 'static,
) -> impl Fn(Request<Body>) -> ResponseFuture + Clone {
move |_| {
let response = on_token_review();
let response_len = response.len();

let mut response = Response::new(response.into());
response
.headers_mut()
.typed_insert(&ContentLength(response_len as u64));
response
.headers_mut()
.typed_insert(&ContentType(mime::APPLICATION_JSON));
Box::new(future::ok(response)) as ResponseFuture
}
}

fn get_service_account_with_annotations_handler(
) -> impl Fn(Request<Body>) -> ResponseFuture + Clone {
move |_| {
response(StatusCode::OK, || {
json!({
"kind": "ServiceAccount",
"apiVersion": "v1",
"metadata": {
"name": "edgeagent",
"namespace": "my-namespace",
"annotations": {
"net.azure-devices.edge.original-moduleid": "$edgeAgent"
}
}
})
.to_string()
})
}
}

fn get_service_account_without_annotations_handler(
) -> impl Fn(Request<Body>) -> ResponseFuture + Clone {
move |_| {
response(StatusCode::OK, || {
json!({
"kind": "ServiceAccount",
"apiVersion": "v1",
"metadata": {
"name": "edgeagent",
"namespace": "my-namespace",
}
})
.to_string()
})
}
}
}
Loading

0 comments on commit 0ae9a09

Please sign in to comment.