diff --git a/src/api.rs b/src/api.rs index df264e3..c5ba1cb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -5,7 +5,7 @@ use std::net::SocketAddr; use std::path::{self, PathBuf}; use std::sync::Arc; -use crate::certs::{load_cert, read_certs, read_pubkey, store_cert}; +use crate::certs::{load_cert_by_id, read_certs, read_pubkey, store_cert}; use anyhow::Context; use axum::body::{self}; use axum::extract::{Path, State}; @@ -102,8 +102,8 @@ pub async fn run( let app = Router::new() .typed_get(get_certs_identifier) + .typed_put(put_cert_update) .route("/certs/:identifier", post(post_certs_identifier)) - .route("/certs/:identifier", put(put_certs_identifier_update)) .layer(ServiceBuilder::new().map_request_body(body::boxed)) .with_state(state); @@ -177,21 +177,25 @@ async fn post_certs_identifier( unimplemented!() } +#[derive(TypedPath)] +#[typed_path("/cert")] +pub struct PutCert; + /// Upload an cert with an higher serial than the previous -async fn put_certs_identifier_update( +async fn put_cert_update( + _: PutCert, State(ApiState { ca, cert_dir, certs, .. }): State, - Path(identifier): Path, CertificateBody(cert): CertificateBody, ) -> ApiResult { cert.validate(&[ca.fingerprint(Default::default())]) .map_err(|_| ApiError::Invalid)?; let _string_repr = cert.to_openssh(); - let prev = load_cert(&cert_dir, &ca, &identifier).await?; + let prev = load_cert_by_id(&cert_dir, &ca, &cert.key_id()).await?; let mut prev_serial = 0; let serial = cert.serial(); if let Some(prev) = prev { diff --git a/src/certs.rs b/src/certs.rs index 9084963..54b7289 100644 --- a/src/certs.rs +++ b/src/certs.rs @@ -1,8 +1,12 @@ use anyhow::Context; use ssh_key::{Certificate, PublicKey}; -use std::{path::{Path, PathBuf}, fmt::Debug}; +use std::{ + fmt::Debug, + io::ErrorKind, + path::{Path, PathBuf}, +}; use tokio::fs; -use tracing::{trace, instrument}; +use tracing::{instrument, trace}; pub async fn read_certs( ca: &PublicKey, @@ -31,13 +35,10 @@ pub async fn read_dir(path: impl AsRef + Debug) -> anyhow::Result String { #[instrument] fn cert_path(ca: &PublicKey, identifier: &str) -> String { - let _ca_fingerprint = ca.fingerprint(Default::default()); format!("{}/{}-cert.pub", ca_dir(ca), identifier) } @@ -80,21 +80,23 @@ pub async fn store_cert( Ok(path) } -pub async fn load_cert( +pub async fn load_cert_by_id( cert_dir: impl AsRef, ca: &PublicKey, identifier: &str, ) -> anyhow::Result> { let path = cert_dir.as_ref().join(cert_path(ca, identifier)); - if !path.exists() { - trace!("no certificate at {:?}", path); - return Ok(None); - } - let contents = fs::read(&path) - .await - .with_context(|| format!("read {:?}", &path))?; + load_cert(&path).await +} + +pub async fn load_cert(file: impl AsRef + Debug) -> anyhow::Result> { + let contents = match fs::read(&file).await { + Ok(contents) => contents, + Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None), + Err(e) => return Err(e).with_context(|| format!("read {:?}", &file)), + }; let string_repr = parse_utf8(contents)?; Ok(Some(Certificate::from_openssh(&string_repr).with_context( - || format!("parse {:?} as openssh certificate", &path), + || format!("parse {:?} as openssh certificate", &file), )?)) } diff --git a/src/client.rs b/src/client.rs index aa7bc48..1bbf2d9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,13 +1,16 @@ +use anyhow::{bail, Context}; use axum_extra::routing::TypedPath; use clap::{Args, Parser, Subcommand}; use reqwest::{Client, StatusCode}; use ssh_key::Certificate; use std::path::PathBuf; use tokio::fs; -use tracing::{debug, info, instrument}; +use tracing::{debug, error, info, instrument}; use tracing::{info_span, Instrument}; use url::Url; +use crate::api::PutCert; +use crate::certs::load_cert; use crate::{ api::GetCert, certs::{self, read_dir}, @@ -61,10 +64,38 @@ async fn upload( files, }: UploadArgs, ) -> anyhow::Result<()> { + let client = reqwest::Client::new(); + let mut certs = Vec::new(); + for path in files.into_iter() { + if let Some(cert) = load_cert(&path).await? { + certs.push(cert); + } + } + let uploads = certs.into_iter().map(|cert| { + let client = client.clone(); + let path = PutCert; + let url = api.join(path.to_uri().path()).unwrap(); + tokio::spawn(async move { upload_cert(client, url, cert).await }) + }); + for upload in uploads { + let _ = upload.await; + } Ok(()) } +#[instrument(skip(client, cert))] async fn upload_cert(client: Client, url: Url, cert: Certificate) -> anyhow::Result<()> { + let resp = client + .put(url.clone()) + .body(cert.to_openssh()?) + .send() + .await?; + let status = resp.status(); + if ![StatusCode::OK, StatusCode::CREATED].contains(&status) { + let id = cert.key_id(); + error!(%id, %status, "failed to upload cert"); + bail!("failed to upload {id}, error code {status}"); + } Ok(()) } @@ -98,7 +129,7 @@ async fn fetch( Ok(()) } -#[instrument] +#[instrument(skip(client, current))] async fn fetch_cert( client: Client, url: Url,