refactor: split
This commit is contained in:
124
common/src/certs.rs
Normal file
124
common/src/certs.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use anyhow::Context;
|
||||
use ssh_key::{Certificate, PublicKey};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
io::ErrorKind,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tokio::fs;
|
||||
use tracing::{instrument, trace};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CertError {
|
||||
#[error("missing key id")]
|
||||
NoKID,
|
||||
#[error("missing ca identifier (comment)")]
|
||||
NoCAComment,
|
||||
}
|
||||
|
||||
pub async fn read_certs(
|
||||
ca: &PublicKey,
|
||||
path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<Vec<Certificate>> {
|
||||
let ca_dir = path.as_ref().join(ca_dir(ca)?);
|
||||
if !ca_dir.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
read_dir(&ca_dir).await
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn read_dir(path: impl AsRef<Path> + Debug) -> anyhow::Result<Vec<Certificate>> {
|
||||
let mut dir = fs::read_dir(path.as_ref())
|
||||
.await
|
||||
.with_context(|| format!("read certs dir '{:?}'", path.as_ref()))?;
|
||||
let mut certs = Vec::new();
|
||||
while let Some(entry) = dir.next_entry().await? {
|
||||
//TODO: investigate why path().ends_with doesn't work
|
||||
if !entry
|
||||
.file_name()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.ends_with("-cert.pub")
|
||||
{
|
||||
trace!(
|
||||
"skipped {:?} due to missing '-cert.pub' extension",
|
||||
entry.path()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let cert = load_cert(entry.path()).await?;
|
||||
if let Some(cert) = cert {
|
||||
certs.push(cert);
|
||||
}
|
||||
}
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
fn parse_utf8(bytes: Vec<u8>) -> anyhow::Result<String> {
|
||||
String::from_utf8(bytes).context("invalid utf-8")
|
||||
}
|
||||
|
||||
pub async fn read_pubkey(path: impl AsRef<Path>) -> anyhow::Result<PublicKey> {
|
||||
let contents = fs::read(&path)
|
||||
.await
|
||||
.with_context(|| format!("read {:?}", path.as_ref()))?;
|
||||
let string_repr = parse_utf8(contents)?;
|
||||
PublicKey::from_openssh(&string_repr)
|
||||
.with_context(|| format!("parse '{}' as public key", string_repr))
|
||||
}
|
||||
|
||||
fn ca_dir(ca: &PublicKey) -> Result<String, CertError> {
|
||||
if ca.comment().is_empty() {
|
||||
return Err(CertError::NoCAComment);
|
||||
}
|
||||
Ok(ca.comment().to_string())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn cert_path(ca: &PublicKey, identifier: &str) -> Result<String, CertError> {
|
||||
if identifier.is_empty() {
|
||||
return Err(CertError::NoKID);
|
||||
}
|
||||
Ok(format!("{}/{}-cert.pub", ca_dir(ca)?, identifier))
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn store_cert(
|
||||
cert_dir: impl AsRef<Path> + Debug,
|
||||
ca: &PublicKey,
|
||||
cert: &Certificate,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
// TODO: proper store
|
||||
let path = cert_dir.as_ref().join(cert_path(&ca, cert.key_id())?);
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("mkdir -p {parent:?}"))?;
|
||||
}
|
||||
fs::write(&path, cert.to_openssh().context("encode cert")?)
|
||||
.await
|
||||
.context("write cert")?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
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)?);
|
||||
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", &file),
|
||||
)?))
|
||||
}
|
6
common/src/lib.rs
Normal file
6
common/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod certs;
|
||||
mod routes;
|
||||
mod util;
|
||||
|
||||
pub use certs::*;
|
||||
pub use routes::*;
|
29
common/src/routes.rs
Normal file
29
common/src/routes.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use async_trait::async_trait;
|
||||
use axum_extra::routing::TypedPath;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(TypedPath, Deserialize)]
|
||||
#[typed_path("/certs")]
|
||||
pub struct CertList;
|
||||
|
||||
#[derive(TypedPath, Deserialize)]
|
||||
#[typed_path("/certs/:identifier")]
|
||||
pub struct GetCert {
|
||||
pub identifier: String,
|
||||
}
|
||||
|
||||
#[derive(TypedPath, Deserialize)]
|
||||
#[typed_path("/certs/:identifier/info")]
|
||||
pub struct GetCertInfo {
|
||||
pub identifier: String,
|
||||
}
|
||||
|
||||
#[derive(TypedPath, Deserialize)]
|
||||
#[typed_path("/certs/:identifier")]
|
||||
pub struct PostCertInfo {
|
||||
pub identifier: String,
|
||||
}
|
||||
|
||||
#[derive(TypedPath)]
|
||||
#[typed_path("/cert")]
|
||||
pub struct PutCert;
|
6
common/src/util.rs
Normal file
6
common/src/util.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#[macro_export]
|
||||
macro_rules! env_key {
|
||||
( $var:expr ) => {
|
||||
concat!("SSH_CD_", $var)
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user