sshca/server/tests/test_host.rs

210 lines
6.9 KiB
Rust

mod common;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use form_data_builder::FormData;
use ssh_key::{Algorithm, Certificate};
use tower::ServiceExt;
use uuid::uuid;
use sshca::server::make_app;
use common::setup;
use common::token;
const ED25519_KEY: &str = concat!(
"ssh-ed25519 ",
"AAAAC3NzaC1lZDI1NTE5AAAAIAsFEmrNIoRHHUayEO0NdAIgtMvci/wME07h+A5XSNJy",
);
const DSA_KEY: &str = concat!(
"ssh-dss ",
"AAAAB3NzaC1kc3MAAACBALNAS+fZjaWt4q+MAgjf6HREFoYjgoSVJUUCtNmRGhND85msVtla",
"kll2gLzL6n6TWyiToARlThoTFu1ZDoGYauDL7iDXrGB6VWJEOQZ3TEMHLFYPziW02AbjR9GI",
"ptsF42D0bTTvvaIaBIhOTjWAUjuFIhAKhPkcj+udIcyH8CG1AAAAFQCpbXQSlxOvd4J92j2C",
"rWDYVGoK8wAAAIAfHiV6/glGZrDRztJmw1hfwbmiNPxaoSGkB+Necfkj0fZrlyLj8sLJIbGQ",
"w0dJMATZdRHw3Ql4R5IOu7sBfX1KQW++onT4ads/Xtl6vwfsjO2e/a6Y1ib9JCIOGJxNAAUC",
"JU0Fm0TSv2Nn6UTICAarp1eKALimqkvy1+ygBWjprgAAAIEAic5EpZH9wpgzvl9kPW531yrz",
"IOlCcXsJFPqQxUThrB2o1g3Rjpscd9kCw5UlPu6GGLk4aSN3UxeIKymTuKiEi7tvP1Tj/Bv5",
"tEc4rhfmrBAfAST09oRFDsELufsOAlTrJ0uk2LhtN14H1RBv9qPR5PQKTEYslyvXG1f8itNQ",
"YnQ="
);
fn make_test_request_body(key: &[u8], name: &str) -> (Body, String) {
let mut form = FormData::new(Vec::new());
form.write_file(
"pubkey",
key,
Some(name.as_ref()),
"application/octet-stream",
)
.unwrap();
let content_type = form.content_type_header();
let body = Body::from(form.finish().unwrap());
(body, content_type)
}
fn make_test_request(body: Body, content_type: &str) -> Request<Body> {
let hostname = "test.example.org";
let machine_id = uuid!("b75e9126-d73a-4ae0-9a0d-63cb3552e6cd");
let token = token::make_token(hostname, machine_id);
Request::builder()
.uri("/host/sign")
.method("POST")
.header("Authorization", format!("Bearer {}", token))
.header("Host", "sshca.example.org")
.header("Content-Type", content_type)
.body(body)
.unwrap()
}
#[tokio::test]
async fn test_sign() {
let (ctx, config) = setup::setup().await.unwrap();
let app = make_app(config);
let (body, content_type) = make_test_request_body(
ED25519_KEY.as_bytes(),
"ssh_host_ed25519_key.pub",
);
let req = make_test_request(body, &content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
let cert = Certificate::from_openssh(std::str::from_utf8(&body).unwrap())
.unwrap();
assert_eq!(cert.algorithm(), Algorithm::Ed25519);
cert.validate(&[ctx.host_ca_fingerprint()]).unwrap();
}
#[tokio::test]
async fn test_sign_invalid() {
let (_ctx, config) = setup::setup().await.unwrap();
let (body, content_type) = make_test_request_body(
"this is not a valid openssh key".as_bytes(),
"ssh_host_ecdsa_key.pub",
);
let app = make_app(config);
let req = make_test_request(body, &content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
assert_eq!(
body,
concat!(
"Could not parse SSH key: ",
"Base64 encoding error: invalid Base64 encoding",
)
);
}
#[tokio::test]
async fn test_sign_nokey() {
let (_ctx, config) = setup::setup().await.unwrap();
let mut form = FormData::new(Vec::new());
let content_type = form.content_type_header();
let body = Body::from(form.finish().unwrap());
let app = make_app(config);
let req = make_test_request(body, &content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
assert_eq!(body, "No SSH public key provided in request",);
}
#[tokio::test]
async fn test_sign_mangled() {
let (_ctx, config) = setup::setup().await.unwrap();
let app = make_app(config);
let mut form = FormData::new(Vec::new());
form.write_file(
"pubkey",
ED25519_KEY.as_bytes(),
Some("ssh_host_ed25519_key.pub".as_ref()),
"application/octet-stream",
)
.unwrap();
let content_type = form.content_type_header();
let mut form_bytes = form.finish().unwrap();
form_bytes.truncate(19);
let body = Body::from(form_bytes);
let req = make_test_request(body, &content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
assert_eq!(body, "Error parsing `multipart/form-data` request",);
}
#[tokio::test]
async fn test_sign_bad_request() {
let (_ctx, config) = setup::setup().await.unwrap();
let app = make_app(config);
let content_type = "text/plain";
let body = Body::from("test");
let req = make_test_request(body, content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
assert_eq!(body, "Invalid `boundary` for `multipart/form-data` request",);
}
#[tokio::test]
async fn test_sign_dsa() {
let (_ctx, config) = setup::setup().await.unwrap();
let app = make_app(config);
let (body, content_type) =
make_test_request_body(DSA_KEY.as_bytes(), "ssh_host_dsa_key.pub");
let req = make_test_request(body, &content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
assert_eq!(body, "Unsupported key algorithm: ssh-dss");
}
#[tokio::test]
async fn test_sign_failure() {
let (_ctx, mut config) = setup::setup().await.unwrap();
config.ca.host.private_key_file = "bogus".into();
let app = make_app(config);
let (body, content_type) = make_test_request_body(
ED25519_KEY.as_bytes(),
"ssh_host_ed25519_key.pub",
);
let req = make_test_request(body, &content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
assert_eq!(body, "Service Unavailable");
}
#[tokio::test]
async fn test_sign_unauthorized() {
// Deliberately drop the TestContext so the machine ID file gets deleted,
// which will cause authentication to fail.
let (_, config) = setup::setup().await.unwrap();
let app = make_app(config);
let (body, content_type) = make_test_request_body(
ED25519_KEY.as_bytes(),
"ssh_host_ed25519_key.pub",
);
let req = make_test_request(body, &content_type);
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
let body = hyper::body::to_bytes(res.into_body()).await.unwrap();
assert_eq!(body, "Unauthorized");
}