refactor(client): make authentication explicit and redact HMAC keys
This commit is contained in:
18 files changed
+492
-119
No files matched your search
@@ -0,0 +1,212 @@
|
||||
#![cfg(feature = "tokio")]
|
||||
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
|
||||
use irtt_client::{managed::*, Authentication, ClientConfig, ClientEvent, HmacKey};
|
||||
use irtt_server::{Server, ServerConfig};
|
||||
|
||||
fn config(auth: Authentication) -> ManagedClientConfig {
|
||||
ManagedClientConfig {
|
||||
client: ClientConfig {
|
||||
auth,
|
||||
duration: None,
|
||||
interval: Duration::from_millis(20),
|
||||
probe_timeout: Duration::from_millis(100),
|
||||
open_timeouts: vec![Duration::from_millis(200)],
|
||||
..ClientConfig::default()
|
||||
},
|
||||
completion: ManagedCompletionPolicy::ExplicitStop,
|
||||
final_drain: Duration::ZERO,
|
||||
..ManagedClientConfig::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime() -> tokio::runtime::Runtime {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_authentication_inherits_disables_and_overrides_on_creation_and_update() {
|
||||
runtime().block_on(async {
|
||||
tokio::time::timeout(Duration::from_secs(5), async {
|
||||
let shared = Authentication::Hmac(HmacKey::new(b"shared-test-key".as_slice()));
|
||||
let replacement =
|
||||
Authentication::Hmac(HmacKey::new(b"replacement-test-key".as_slice()));
|
||||
let empty = Authentication::Hmac(HmacKey::new(Vec::new()));
|
||||
let mut targets = Vec::new();
|
||||
let mut servers = Vec::new();
|
||||
for (id, key, auth) in [
|
||||
(
|
||||
"inherit",
|
||||
Some(b"shared-test-key".as_slice()),
|
||||
TargetAuth::Inherit,
|
||||
),
|
||||
(
|
||||
"disable",
|
||||
None,
|
||||
TargetAuth::Override(Authentication::Unauthenticated),
|
||||
),
|
||||
(
|
||||
"replace",
|
||||
Some(b"replacement-test-key".as_slice()),
|
||||
TargetAuth::Override(replacement),
|
||||
),
|
||||
("empty", Some(&[]), TargetAuth::Override(empty)),
|
||||
] {
|
||||
let server_config = key.map_or_else(ServerConfig::default, |key| {
|
||||
ServerConfig::default().with_hmac_key(key)
|
||||
});
|
||||
let mut server = Server::bind("127.0.0.1:0".parse().unwrap(), server_config)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut target =
|
||||
ManagedTargetConfig::new(id, server.local_addr().unwrap().to_string());
|
||||
target.auth = auth;
|
||||
targets.push(target);
|
||||
servers.push(tokio::spawn(async move {
|
||||
server.run(std::future::pending()).await
|
||||
}));
|
||||
}
|
||||
for initial in [true, false] {
|
||||
let (task, handle) = ManagedClient::task(
|
||||
config(shared.clone()),
|
||||
if initial { targets.clone() } else { vec![] },
|
||||
)
|
||||
.unwrap();
|
||||
let mut events = handle.subscribe().unwrap();
|
||||
let run = tokio::spawn(task);
|
||||
if !initial {
|
||||
handle
|
||||
.update_targets(targets.clone())
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
let mut replied = HashSet::new();
|
||||
while replied.len() < targets.len() {
|
||||
if let ManagedEvent::Client {
|
||||
target,
|
||||
event: ClientEvent::EchoReply { .. },
|
||||
} = events.recv().await.unwrap()
|
||||
{
|
||||
replied.insert(target.id);
|
||||
}
|
||||
}
|
||||
handle.stop().await;
|
||||
let outcome = run.await.unwrap();
|
||||
assert_eq!(outcome.end_reason, ManagedEndReason::StopRequested);
|
||||
assert_eq!(outcome.successful_target_outcomes, targets.len() as u64);
|
||||
assert!(outcome.recent_target_outcomes.iter().all(|target| {
|
||||
target.replies_received > 0 && target.cleanup_failure.is_none()
|
||||
}));
|
||||
}
|
||||
for server in servers {
|
||||
server.abort();
|
||||
assert!(server.await.unwrap_err().is_cancelled());
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("every managed authentication mode must receive replies");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changing_auth_specification_replaces_generation_even_when_resolved_auth_is_equal() {
|
||||
runtime().block_on(async {
|
||||
tokio::time::timeout(Duration::from_secs(5), async {
|
||||
for auth in [
|
||||
Authentication::Unauthenticated,
|
||||
Authentication::Hmac(HmacKey::new(b"shared-test-key".as_slice())),
|
||||
] {
|
||||
let server_config = match &auth {
|
||||
Authentication::Unauthenticated => ServerConfig::default(),
|
||||
Authentication::Hmac(key) => {
|
||||
ServerConfig::default().with_hmac_key(key.as_bytes())
|
||||
}
|
||||
};
|
||||
let mut server = Server::bind("127.0.0.1:0".parse().unwrap(), server_config)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut configured =
|
||||
ManagedTargetConfig::new("one", server.local_addr().unwrap().to_string());
|
||||
let service = tokio::spawn(async move { server.run(std::future::pending()).await });
|
||||
let (task, handle) =
|
||||
ManagedClient::task(config(auth.clone()), vec![configured.clone()]).unwrap();
|
||||
let mut events = handle.subscribe().unwrap();
|
||||
let run = tokio::spawn(task);
|
||||
let mut current = handle.status().targets[0].target.clone();
|
||||
// Wait for a real reply before changing the running session.
|
||||
loop {
|
||||
if matches!(events.recv().await.unwrap(), ManagedEvent::Client {
|
||||
target, event: ClientEvent::EchoReply { .. }
|
||||
} if target == current)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
for specification in [TargetAuth::Override(auth.clone()), TargetAuth::Inherit] {
|
||||
configured.auth = specification;
|
||||
let applied = handle
|
||||
.update_targets(vec![configured.clone()])
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
let next = applied
|
||||
.status
|
||||
.targets
|
||||
.iter()
|
||||
.find(|target| target.desired)
|
||||
.unwrap()
|
||||
.target
|
||||
.clone();
|
||||
assert!(next.generation > current.generation);
|
||||
let mut retired = false;
|
||||
let mut replied = false;
|
||||
while !retired || !replied {
|
||||
match events.recv().await.unwrap() {
|
||||
ManagedEvent::TargetFinished { outcome }
|
||||
if outcome.target == current =>
|
||||
{
|
||||
assert_eq!(outcome.end_reason, ManagedTargetEndReason::Replaced);
|
||||
assert!(outcome.cleanup_failure.is_none());
|
||||
retired = true;
|
||||
}
|
||||
ManagedEvent::Client {
|
||||
target,
|
||||
event: ClientEvent::EchoReply { .. },
|
||||
} if target == next => {
|
||||
replied = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let identical = handle
|
||||
.update_targets(vec![configured.clone()])
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
identical
|
||||
.status
|
||||
.targets
|
||||
.iter()
|
||||
.find(|target| target.desired)
|
||||
.unwrap()
|
||||
.target,
|
||||
next
|
||||
);
|
||||
current = next;
|
||||
}
|
||||
handle.stop().await;
|
||||
assert_eq!(run.await.unwrap().successful_target_outcomes, 3);
|
||||
service.abort();
|
||||
assert!(service.await.unwrap_err().is_cancelled());
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("auth specification changes must replace and reopen target generations");
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user