mod common;
use std::time::Duration;
use gunnar_ssh::Access;
use common::{
authenticate, authorized_keys_line, fingerprint_of, mint_key, plane_with_key, start,
start_with_key, Plane,
};
#[test]
fn the_fingerprint_russh_computes_is_the_one_the_auth_table_stored() {
let key = mint_key();
let plane = plane_with_key(&key, Access::Read);
let offered = fingerprint_of(&key);
let stored = plane.auth().keys().expect("keys");
assert_eq!(stored.len(), 1);
assert_eq!(
stored[0].fingerprint_sha256,
offered.as_str(),
"the fingerprint russh's ssh-key 0.7 produces is not the one gunnar-core's 0.6 stored. \
The version split is not bridged and no key could ever authenticate"
);
let found = plane
.control
.principal_for_key(&offered)
.expect("the table is readable")
.expect("the offered key must identify its principal");
assert_eq!(found.id, "principal-1");
let stranger = mint_key();
assert!(
plane
.control
.principal_for_key(&fingerprint_of(&stranger))
.expect("readable")
.is_none(),
"an unregistered key identified somebody"
);
assert_ne!(fingerprint_of(&stranger), offered);
}
#[tokio::test]
async fn a_registered_key_authenticates() {
let key = mint_key();
let fixture = start_with_key(&key, Access::Write).await;
let addr = fixture.server.local_addr();
let session = authenticate(addr, &key, "git")
.await
.expect("connect")
.expect("a registered key must authenticate");
drop(session);
fixture
.server
.shutdown("test over")
.await
.expect("shutdown");
}
#[tokio::test]
async fn an_unregistered_key_is_refused() {
let registered = mint_key();
let stranger = mint_key();
assert_ne!(
fingerprint_of(®istered).as_str(),
fingerprint_of(&stranger).as_str()
);
let fixture = start_with_key(®istered, Access::Write).await;
let addr = fixture.server.local_addr();
let outcome = authenticate(addr, &stranger, "git").await.expect("connect");
assert!(
outcome.is_none(),
"an unregistered key must NOT authenticate"
);
fixture
.server
.shutdown("test over")
.await
.expect("shutdown");
}
#[tokio::test]
async fn registering_that_same_key_turns_the_refusal_green() {
let stranger = mint_key();
let plane = Plane::empty()
.with_the_repo()
.with_principal("principal-1")
.with_key("principal-1", &stranger)
.with_grant("principal-1", Access::Read);
let fixture = start(plane.control, None).await;
let outcome = authenticate(fixture.server.local_addr(), &stranger, "git")
.await
.expect("connect");
assert!(
outcome.is_some(),
"the very same key must authenticate once it is registered, or the refusal test \
proves nothing about the identity table"
);
fixture
.server
.shutdown("test over")
.await
.expect("shutdown");
}
#[tokio::test]
async fn the_ssh_username_is_decorative() {
let key = mint_key();
let fixture = start_with_key(&key, Access::Read).await;
let addr = fixture.server.local_addr();
for user in ["git", "alice", "root", "", "principal-1"] {
let session = authenticate(addr, &key, user).await.expect("connect");
assert!(
session.is_some(),
"the username {user:?} should have made no difference"
);
}
let stranger = mint_key();
assert!(
authenticate(addr, &stranger, "principal-1")
.await
.expect("connect")
.is_none(),
"claiming a registered principal's name must not authenticate an unregistered key"
);
fixture
.server
.shutdown("test over")
.await
.expect("shutdown");
}
#[tokio::test]
async fn revoking_a_key_stops_it_authenticating() {
let key = mint_key();
let plane = plane_with_key(&key, Access::Read);
let auth = plane.auth().clone();
let fixture = start(plane.control, None).await;
let addr = fixture.server.local_addr();
assert!(
authenticate(addr, &key, "git")
.await
.expect("connect")
.is_some(),
"a registered key must authenticate before it is revoked"
);
let revoked = auth
.revoke_key(&fingerprint_of(&key))
.expect("revoke")
.expect("the key was there to revoke");
assert_eq!(revoked.openssh.trim(), authorized_keys_line(&key).trim());
assert!(
authenticate(addr, &key, "git")
.await
.expect("connect")
.is_none(),
"a revoked key must not authenticate, on the SAME running listener"
);
fixture
.server
.shutdown("test over")
.await
.expect("shutdown");
}
#[tokio::test]
async fn removing_the_principal_stops_its_key_authenticating() {
let key = mint_key();
let plane = plane_with_key(&key, Access::Read);
let auth = plane.auth().clone();
let fixture = start(plane.control, None).await;
let addr = fixture.server.local_addr();
assert!(authenticate(addr, &key, "git")
.await
.expect("connect")
.is_some());
auth.remove_principal("principal-1").expect("remove");
assert!(
authenticate(addr, &key, "git")
.await
.expect("connect")
.is_none(),
"a key that outlives its principal is an account that still authenticates"
);
fixture
.server
.shutdown("test over")
.await
.expect("shutdown");
}
#[tokio::test]
async fn an_authenticated_session_that_never_execs_is_disconnected() {
let key = mint_key();
let fixture = start(
plane_with_key(&key, Access::Read).control,
Some(Duration::from_millis(300)),
)
.await;
let session = authenticate(fixture.server.local_addr(), &key, "git")
.await
.expect("connect")
.expect("authenticate");
let mut channel = session
.channel_open_session()
.await
.expect("open a session channel");
let ended = tokio::time::timeout(Duration::from_secs(5), async {
while channel.wait().await.is_some() {}
})
.await;
assert!(
ended.is_ok(),
"the session was still alive well after the exec grace expired"
);
fixture
.server
.shutdown("test over")
.await
.expect("shutdown");
}