added: client upload

This commit is contained in:
2022-12-01 22:48:18 +01:00
parent 93337a9b46
commit 94a69e4a53
3 changed files with 63 additions and 26 deletions

View File

@@ -5,7 +5,7 @@ use std::net::SocketAddr;
use std::path::{self, PathBuf}; use std::path::{self, PathBuf};
use std::sync::Arc; 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 anyhow::Context;
use axum::body::{self}; use axum::body::{self};
use axum::extract::{Path, State}; use axum::extract::{Path, State};
@@ -102,8 +102,8 @@ pub async fn run(
let app = Router::new() let app = Router::new()
.typed_get(get_certs_identifier) .typed_get(get_certs_identifier)
.typed_put(put_cert_update)
.route("/certs/:identifier", post(post_certs_identifier)) .route("/certs/:identifier", post(post_certs_identifier))
.route("/certs/:identifier", put(put_certs_identifier_update))
.layer(ServiceBuilder::new().map_request_body(body::boxed)) .layer(ServiceBuilder::new().map_request_body(body::boxed))
.with_state(state); .with_state(state);
@@ -177,21 +177,25 @@ async fn post_certs_identifier(
unimplemented!() unimplemented!()
} }
#[derive(TypedPath)]
#[typed_path("/cert")]
pub struct PutCert;
/// Upload an cert with an higher serial than the previous /// Upload an cert with an higher serial than the previous
async fn put_certs_identifier_update( async fn put_cert_update(
_: PutCert,
State(ApiState { State(ApiState {
ca, ca,
cert_dir, cert_dir,
certs, certs,
.. ..
}): State<ApiState>, }): State<ApiState>,
Path(identifier): Path<String>,
CertificateBody(cert): CertificateBody, CertificateBody(cert): CertificateBody,
) -> ApiResult<String> { ) -> ApiResult<String> {
cert.validate(&[ca.fingerprint(Default::default())]) cert.validate(&[ca.fingerprint(Default::default())])
.map_err(|_| ApiError::Invalid)?; .map_err(|_| ApiError::Invalid)?;
let _string_repr = cert.to_openssh(); 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 mut prev_serial = 0;
let serial = cert.serial(); let serial = cert.serial();
if let Some(prev) = prev { if let Some(prev) = prev {

View File

@@ -1,8 +1,12 @@
use anyhow::Context; use anyhow::Context;
use ssh_key::{Certificate, PublicKey}; 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 tokio::fs;
use tracing::{trace, instrument}; use tracing::{instrument, trace};
pub async fn read_certs( pub async fn read_certs(
ca: &PublicKey, ca: &PublicKey,
@@ -31,14 +35,11 @@ pub async fn read_dir(path: impl AsRef<Path> + Debug) -> anyhow::Result<Vec<Cert
); );
continue; continue;
} }
let contents = fs::read(&entry.path()) let cert = load_cert(entry.path()).await?;
.await if let Some(cert) = cert {
.with_context(|| format!("read {:?}", entry.path()))?;
let string_repr = parse_utf8(contents)?;
let cert = Certificate::from_openssh(&string_repr)
.with_context(|| format!("parse {:?} as openssh certificate", entry.path()))?;
certs.push(cert); certs.push(cert);
} }
}
Ok(certs) Ok(certs)
} }
@@ -61,7 +62,6 @@ fn ca_dir(ca: &PublicKey) -> String {
#[instrument] #[instrument]
fn cert_path(ca: &PublicKey, identifier: &str) -> String { fn cert_path(ca: &PublicKey, identifier: &str) -> String {
let _ca_fingerprint = ca.fingerprint(Default::default());
format!("{}/{}-cert.pub", ca_dir(ca), identifier) format!("{}/{}-cert.pub", ca_dir(ca), identifier)
} }
@@ -80,21 +80,23 @@ pub async fn store_cert(
Ok(path) Ok(path)
} }
pub async fn load_cert( pub async fn load_cert_by_id(
cert_dir: impl AsRef<Path>, cert_dir: impl AsRef<Path>,
ca: &PublicKey, ca: &PublicKey,
identifier: &str, identifier: &str,
) -> anyhow::Result<Option<Certificate>> { ) -> anyhow::Result<Option<Certificate>> {
let path = cert_dir.as_ref().join(cert_path(ca, identifier)); let path = cert_dir.as_ref().join(cert_path(ca, identifier));
if !path.exists() { load_cert(&path).await
trace!("no certificate at {:?}", path); }
return Ok(None);
} pub async fn load_cert(file: impl AsRef<Path> + Debug) -> anyhow::Result<Option<Certificate>> {
let contents = fs::read(&path) let contents = match fs::read(&file).await {
.await Ok(contents) => contents,
.with_context(|| format!("read {:?}", &path))?; 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)?; let string_repr = parse_utf8(contents)?;
Ok(Some(Certificate::from_openssh(&string_repr).with_context( Ok(Some(Certificate::from_openssh(&string_repr).with_context(
|| format!("parse {:?} as openssh certificate", &path), || format!("parse {:?} as openssh certificate", &file),
)?)) )?))
} }

View File

@@ -1,13 +1,16 @@
use anyhow::{bail, Context};
use axum_extra::routing::TypedPath; use axum_extra::routing::TypedPath;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use reqwest::{Client, StatusCode}; use reqwest::{Client, StatusCode};
use ssh_key::Certificate; use ssh_key::Certificate;
use std::path::PathBuf; use std::path::PathBuf;
use tokio::fs; use tokio::fs;
use tracing::{debug, info, instrument}; use tracing::{debug, error, info, instrument};
use tracing::{info_span, Instrument}; use tracing::{info_span, Instrument};
use url::Url; use url::Url;
use crate::api::PutCert;
use crate::certs::load_cert;
use crate::{ use crate::{
api::GetCert, api::GetCert,
certs::{self, read_dir}, certs::{self, read_dir},
@@ -61,10 +64,38 @@ async fn upload(
files, files,
}: UploadArgs, }: UploadArgs,
) -> anyhow::Result<()> { ) -> 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(()) Ok(())
} }
#[instrument(skip(client, cert))]
async fn upload_cert(client: Client, url: Url, cert: Certificate) -> anyhow::Result<()> { 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(()) Ok(())
} }
@@ -98,7 +129,7 @@ async fn fetch(
Ok(()) Ok(())
} }
#[instrument] #[instrument(skip(client, current))]
async fn fetch_cert( async fn fetch_cert(
client: Client, client: Client,
url: Url, url: Url,