refactor: split

This commit is contained in:
2022-12-24 16:55:42 +01:00
parent e1da57a407
commit 1b8b304ebc
15 changed files with 187 additions and 94 deletions

26
common/Cargo.toml Normal file
View File

@@ -0,0 +1,26 @@
[package]
name = "ssh-cert-dist-common"
version = "0.1.0"
authors = ["shimun <shimun@shimun.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.66"
async-trait = "0.1.59"
axum = { version = "0.6.1" }
axum-extra = { version = "0.4.1", features = ["typed-routing"] }
serde = { version = "1.0.148", features = ["derive"] }
ssh-key = { version = "0.5.1", features = ["ed25519", "p256", "p384", "rsa", "signature"] }
thiserror = "1.0.37"
tokio = { version = "1.22.0", features = ["io-std", "test-util", "tracing", "macros", "fs"] }
tracing = { version = "0.1.37", features = ["release_max_level_debug"] }
tracing-subscriber = "0.3.16"
[patch.crates-io]
ssh-key = { git = "https://github.com/a-dma/SSH.git", branch = "u2f_signatures" }
[dev-dependencies]
tempfile = "3.3.0"

124
common/src/certs.rs Normal file
View 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
View File

@@ -0,0 +1,6 @@
mod certs;
mod routes;
mod util;
pub use certs::*;
pub use routes::*;

29
common/src/routes.rs Normal file
View 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
View File

@@ -0,0 +1,6 @@
#[macro_export]
macro_rules! env_key {
( $var:expr ) => {
concat!("SSH_CD_", $var)
};
}