added: client upload
This commit is contained in:
14
src/api.rs
14
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<ApiState>,
|
||||
Path(identifier): Path<String>,
|
||||
CertificateBody(cert): CertificateBody,
|
||||
) -> ApiResult<String> {
|
||||
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 {
|
||||
|
38
src/certs.rs
38
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,14 +35,11 @@ pub async fn read_dir(path: impl AsRef<Path> + Debug) -> anyhow::Result<Vec<Cert
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let contents = fs::read(&entry.path())
|
||||
.await
|
||||
.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()))?;
|
||||
let cert = load_cert(entry.path()).await?;
|
||||
if let Some(cert) = cert {
|
||||
certs.push(cert);
|
||||
}
|
||||
}
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
@@ -61,7 +62,6 @@ fn ca_dir(ca: &PublicKey) -> 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<Path>,
|
||||
ca: &PublicKey,
|
||||
identifier: &str,
|
||||
) -> anyhow::Result<Option<Certificate>> {
|
||||
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<Path> + Debug) -> anyhow::Result<Option<Certificate>> {
|
||||
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),
|
||||
)?))
|
||||
}
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user