Fetch User Keys - simple tool for fetching SSH keys from various sources

ft: basic functionality

hauleth.dev 55292572 5e5bedc5

verified
Changed files
+248 -34
src
+107
Cargo.lock
···
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
+
name = "async-trait"
+
version = "0.1.77"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
+
+
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
+
name = "erased-serde"
+
version = "0.4.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "55d05712b2d8d88102bc9868020c9e5c7a1f5527c452b9b97450a1d006140ba7"
+
dependencies = [
+
"serde",
+
]
+
+
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
name = "fuk"
version = "0.1.0"
dependencies = [
+
"async-trait",
"futures",
"reqwest",
+
"serde",
+
"serde_json",
"tokio",
"tokio-stream",
+
"toml",
+
"typetag",
]
[[package]]
···
]
[[package]]
+
name = "inventory"
+
version = "0.3.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767"
+
+
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "serde_spanned"
+
version = "0.6.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+
dependencies = [
+
"serde",
+
]
+
+
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "toml"
+
version = "0.8.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
+
dependencies = [
+
"serde",
+
"serde_spanned",
+
"toml_datetime",
+
"toml_edit",
+
]
+
+
[[package]]
+
name = "toml_datetime"
+
version = "0.6.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+
dependencies = [
+
"serde",
+
]
+
+
[[package]]
+
name = "toml_edit"
+
version = "0.22.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951"
+
dependencies = [
+
"indexmap",
+
"serde",
+
"serde_spanned",
+
"toml_datetime",
+
"winnow",
+
]
+
+
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+
[[package]]
+
name = "typetag"
+
version = "0.2.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c43148481c7b66502c48f35b8eef38b6ccdc7a9f04bd4cc294226d901ccc9bc7"
+
dependencies = [
+
"erased-serde",
+
"inventory",
+
"once_cell",
+
"serde",
+
"typetag-impl",
+
]
+
+
[[package]]
+
name = "typetag-impl"
+
version = "0.2.15"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "291db8a81af4840c10d636e047cac67664e343be44e24dfdbd1492df9a5d3390"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"syn",
+
]
[[package]]
name = "unicode-bidi"
···
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+
[[package]]
+
name = "winnow"
+
version = "0.5.39"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29"
+
dependencies = [
+
"memchr",
+
]
[[package]]
name = "winreg"
+5
Cargo.toml
···
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+
async-trait = "0.1.77"
futures = "0.3.30"
reqwest = "0.11.24"
+
serde = { version = "1.0.196", features = ["derive"] }
+
serde_json = "1.0.113"
tokio = { version = "1.36.0", default-features = false, features = ["rt-multi-thread", "fs", "io-std", "io-util", "macros", "process", "net"] }
tokio-stream = "0.1.14"
+
toml = "0.8.10"
+
typetag = "0.2.15"
+15 -1
flake.nix
···
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
-
perSystem = { inputs', ... }: {
+
perSystem = { inputs', pkgs, ... }: {
devenv.shells.default = {
languages.rust.enable = true;
+
+
packages =
+
[
+
pkgs.scdoc
+
pkgs.cargo-nextest
+
pkgs.cargo-outdated
+
]
+
++ pkgs.lib.lists.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk; [
+
frameworks.Foundation
+
frameworks.CoreFoundation
+
frameworks.SystemConfiguration
+
frameworks.Security
+
pkgs.libiconv
+
]);
};
};
};
+19 -29
src/config.rs
···
use crate::output::Output;
+
use crate::sources::*;
+
use futures::prelude::*;
-
struct Entry {
-
name: String,
-
keys: Vec<Source>,
+
#[derive(Debug, serde::Deserialize)]
+
pub struct Entry {
+
pub name: String,
+
pub keys: Vec<Box<dyn Fetch>>,
}
impl Entry {
-
fn fetch(&self) -> (String, impl Stream<Item=String> + '_) {
-
let stream =
-
stream::iter(&self.keys)
-
.flat_map(Source::fetch);
+
pub async fn fetch(&self) -> (String, Vec<String>) {
+
let stream = stream::iter(&self.keys)
+
.then(|k| async { k.fetch().await })
+
.map(stream::iter)
+
.flatten()
+
.collect()
+
.await;
(self.name.clone(), stream)
}
}
-
enum Source {
-
Raw(String),
-
Sourcehut { user: String },
-
Github { user: String, instance: String },
-
Host(Vec<String>),
-
}
-
-
impl Source {
-
fn fetch(&self) -> impl Stream<Item=String> {
-
match *self {
-
Source::Raw(ref key) => stream::iter(vec![key.clone()]),
-
Source::Host(ref _hosts) => stream::iter(vec![]),
-
_ => unimplemented!(),
-
}
-
}
-
}
-
+
#[derive(Debug, serde::Deserialize)]
pub struct Config {
-
users: Vec<Entry>,
+
#[serde(rename = "entry")]
+
pub entries: Vec<Entry>,
}
impl Config {
pub async fn fetch(&self) -> Result<Output, ()> {
-
let keys =
-
stream::iter(&self.users)
-
.map(Entry::fetch)
+
let keys = stream::iter(&self.entries)
+
.then(Entry::fetch)
.collect()
-
.await;
+
.await;
Ok(Output { keys })
}
+4 -3
src/lib.rs
···
-
pub mod output;
pub mod config;
+
pub mod output;
+
pub mod sources;
-
pub fn fuk() {
-
println!("Fuk");
+
pub async fn fuk(config: config::Config) -> output::Output {
+
config.fetch().await.unwrap()
}
+21 -1
src/main.rs
···
+
const EXAMPLE: &'static str = r#"
+
[[entry]]
+
name = "hauleth"
+
keys = [
+
{ Forge = { user = "~hauleth", host = "meta.sr.ht" } }
+
]
+
+
[[entry]]
+
name = "heimdall"
+
keys = [
+
{ Host = [ "heimdall" ] }
+
]
+
"#;
+
#[tokio::main]
async fn main() {
-
fuk::fuk();
+
let config: fuk::config::Config =
+
toml::from_str(EXAMPLE).unwrap();
+
+
println!("{:#?}", config);
+
+
let output = fuk::fuk(config).await;
+
println!("{}", serde_json::to_string_pretty(&output.keys).unwrap());
}
+1
src/output/mod.rs
···
use std::collections::HashMap;
+
#[derive(Debug, serde::Serialize)]
pub struct Output {
pub keys: HashMap<String, Vec<String>>,
}
+76
src/sources/mod.rs
···
+
use tokio::process::Command;
+
+
use serde::Deserialize;
+
use async_trait::async_trait;
+
+
#[typetag::deserialize]
+
#[async_trait]
+
pub trait Fetch: std::fmt::Debug {
+
async fn fetch(&self) -> Vec<String>;
+
}
+
+
#[derive(Debug, Deserialize)]
+
pub struct Raw(Vec<String>);
+
+
#[typetag::deserialize]
+
#[async_trait::async_trait]
+
impl Fetch for Raw {
+
async fn fetch(&self) -> Vec<String> {
+
self.0.clone()
+
}
+
}
+
+
#[derive(Debug, Deserialize)]
+
pub struct Host(pub Vec<String>);
+
+
#[typetag::deserialize]
+
#[async_trait]
+
impl Fetch for Host {
+
async fn fetch(&self) -> Vec<String> {
+
let result = Command::new("ssh-keyscan")
+
.args(&self.0)
+
.output()
+
.await
+
.unwrap();
+
+
std::str::from_utf8(&result.stdout)
+
.unwrap()
+
.trim()
+
.split('\n')
+
.map(|line| {
+
// Ignore first column as it contain hostname which is not
+
// needed there
+
line.split(' ')
+
.skip(1)
+
.collect::<Vec<_>>()
+
.join(" ")
+
.to_owned()
+
})
+
.collect()
+
}
+
}
+
+
#[derive(Debug, Deserialize)]
+
pub struct Forge {
+
pub user: String,
+
pub host: String,
+
}
+
+
#[typetag::deserialize]
+
#[async_trait]
+
impl Fetch for Forge {
+
async fn fetch(&self) -> Vec<String> {
+
let url = format!("https://{}/{}.keys", self.host, self.user);
+
+
reqwest::get(url)
+
.await
+
.unwrap()
+
.text()
+
.await
+
.unwrap()
+
.trim()
+
.split('\n')
+
.map(|s| s.to_owned())
+
.collect()
+
}
+
}