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

Configure Feed

Select the types of activity you want to include in your feed.

ft: basic functionality

+248 -34
+107
Cargo.lock
··· 18 18 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 19 20 20 [[package]] 21 + name = "async-trait" 22 + version = "0.1.77" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" 25 + dependencies = [ 26 + "proc-macro2", 27 + "quote", 28 + "syn", 29 + ] 30 + 31 + [[package]] 21 32 name = "autocfg" 22 33 version = "1.1.0" 23 34 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 115 126 checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 116 127 117 128 [[package]] 129 + name = "erased-serde" 130 + version = "0.4.2" 131 + source = "registry+https://github.com/rust-lang/crates.io-index" 132 + checksum = "55d05712b2d8d88102bc9868020c9e5c7a1f5527c452b9b97450a1d006140ba7" 133 + dependencies = [ 134 + "serde", 135 + ] 136 + 137 + [[package]] 118 138 name = "errno" 119 139 version = "0.3.8" 120 140 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 164 184 name = "fuk" 165 185 version = "0.1.0" 166 186 dependencies = [ 187 + "async-trait", 167 188 "futures", 168 189 "reqwest", 190 + "serde", 191 + "serde_json", 169 192 "tokio", 170 193 "tokio-stream", 194 + "toml", 195 + "typetag", 171 196 ] 172 197 173 198 [[package]] ··· 388 413 ] 389 414 390 415 [[package]] 416 + name = "inventory" 417 + version = "0.3.15" 418 + source = "registry+https://github.com/rust-lang/crates.io-index" 419 + checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" 420 + 421 + [[package]] 391 422 name = "ipnet" 392 423 version = "2.9.0" 393 424 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 731 762 ] 732 763 733 764 [[package]] 765 + name = "serde_spanned" 766 + version = "0.6.5" 767 + source = "registry+https://github.com/rust-lang/crates.io-index" 768 + checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 769 + dependencies = [ 770 + "serde", 771 + ] 772 + 773 + [[package]] 734 774 name = "serde_urlencoded" 735 775 version = "0.7.1" 736 776 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 900 940 ] 901 941 902 942 [[package]] 943 + name = "toml" 944 + version = "0.8.10" 945 + source = "registry+https://github.com/rust-lang/crates.io-index" 946 + checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" 947 + dependencies = [ 948 + "serde", 949 + "serde_spanned", 950 + "toml_datetime", 951 + "toml_edit", 952 + ] 953 + 954 + [[package]] 955 + name = "toml_datetime" 956 + version = "0.6.5" 957 + source = "registry+https://github.com/rust-lang/crates.io-index" 958 + checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 959 + dependencies = [ 960 + "serde", 961 + ] 962 + 963 + [[package]] 964 + name = "toml_edit" 965 + version = "0.22.4" 966 + source = "registry+https://github.com/rust-lang/crates.io-index" 967 + checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" 968 + dependencies = [ 969 + "indexmap", 970 + "serde", 971 + "serde_spanned", 972 + "toml_datetime", 973 + "winnow", 974 + ] 975 + 976 + [[package]] 903 977 name = "tower-service" 904 978 version = "0.3.2" 905 979 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 929 1003 version = "0.2.5" 930 1004 source = "registry+https://github.com/rust-lang/crates.io-index" 931 1005 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1006 + 1007 + [[package]] 1008 + name = "typetag" 1009 + version = "0.2.15" 1010 + source = "registry+https://github.com/rust-lang/crates.io-index" 1011 + checksum = "c43148481c7b66502c48f35b8eef38b6ccdc7a9f04bd4cc294226d901ccc9bc7" 1012 + dependencies = [ 1013 + "erased-serde", 1014 + "inventory", 1015 + "once_cell", 1016 + "serde", 1017 + "typetag-impl", 1018 + ] 1019 + 1020 + [[package]] 1021 + name = "typetag-impl" 1022 + version = "0.2.15" 1023 + source = "registry+https://github.com/rust-lang/crates.io-index" 1024 + checksum = "291db8a81af4840c10d636e047cac67664e343be44e24dfdbd1492df9a5d3390" 1025 + dependencies = [ 1026 + "proc-macro2", 1027 + "quote", 1028 + "syn", 1029 + ] 932 1030 933 1031 [[package]] 934 1032 name = "unicode-bidi" ··· 1190 1288 version = "0.52.0" 1191 1289 source = "registry+https://github.com/rust-lang/crates.io-index" 1192 1290 checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 1291 + 1292 + [[package]] 1293 + name = "winnow" 1294 + version = "0.5.39" 1295 + source = "registry+https://github.com/rust-lang/crates.io-index" 1296 + checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" 1297 + dependencies = [ 1298 + "memchr", 1299 + ] 1193 1300 1194 1301 [[package]] 1195 1302 name = "winreg"
+5
Cargo.toml
··· 6 6 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 7 8 8 [dependencies] 9 + async-trait = "0.1.77" 9 10 futures = "0.3.30" 10 11 reqwest = "0.11.24" 12 + serde = { version = "1.0.196", features = ["derive"] } 13 + serde_json = "1.0.113" 11 14 tokio = { version = "1.36.0", default-features = false, features = ["rt-multi-thread", "fs", "io-std", "io-util", "macros", "process", "net"] } 12 15 tokio-stream = "0.1.14" 16 + toml = "0.8.10" 17 + typetag = "0.2.15"
+15 -1
flake.nix
··· 24 24 25 25 systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; 26 26 27 - perSystem = { inputs', ... }: { 27 + perSystem = { inputs', pkgs, ... }: { 28 28 devenv.shells.default = { 29 29 languages.rust.enable = true; 30 + 31 + packages = 32 + [ 33 + pkgs.scdoc 34 + pkgs.cargo-nextest 35 + pkgs.cargo-outdated 36 + ] 37 + ++ pkgs.lib.lists.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk; [ 38 + frameworks.Foundation 39 + frameworks.CoreFoundation 40 + frameworks.SystemConfiguration 41 + frameworks.Security 42 + pkgs.libiconv 43 + ]); 30 44 }; 31 45 }; 32 46 };
+19 -29
src/config.rs
··· 1 1 use crate::output::Output; 2 + use crate::sources::*; 3 + 2 4 use futures::prelude::*; 3 5 4 - struct Entry { 5 - name: String, 6 - keys: Vec<Source>, 6 + #[derive(Debug, serde::Deserialize)] 7 + pub struct Entry { 8 + pub name: String, 9 + pub keys: Vec<Box<dyn Fetch>>, 7 10 } 8 11 9 12 impl Entry { 10 - fn fetch(&self) -> (String, impl Stream<Item=String> + '_) { 11 - let stream = 12 - stream::iter(&self.keys) 13 - .flat_map(Source::fetch); 13 + pub async fn fetch(&self) -> (String, Vec<String>) { 14 + let stream = stream::iter(&self.keys) 15 + .then(|k| async { k.fetch().await }) 16 + .map(stream::iter) 17 + .flatten() 18 + .collect() 19 + .await; 14 20 15 21 (self.name.clone(), stream) 16 22 } 17 23 } 18 24 19 - enum Source { 20 - Raw(String), 21 - Sourcehut { user: String }, 22 - Github { user: String, instance: String }, 23 - Host(Vec<String>), 24 - } 25 - 26 - impl Source { 27 - fn fetch(&self) -> impl Stream<Item=String> { 28 - match *self { 29 - Source::Raw(ref key) => stream::iter(vec![key.clone()]), 30 - Source::Host(ref _hosts) => stream::iter(vec![]), 31 - _ => unimplemented!(), 32 - } 33 - } 34 - } 35 - 25 + #[derive(Debug, serde::Deserialize)] 36 26 pub struct Config { 37 - users: Vec<Entry>, 27 + #[serde(rename = "entry")] 28 + pub entries: Vec<Entry>, 38 29 } 39 30 40 31 impl Config { 41 32 pub async fn fetch(&self) -> Result<Output, ()> { 42 - let keys = 43 - stream::iter(&self.users) 44 - .map(Entry::fetch) 33 + let keys = stream::iter(&self.entries) 34 + .then(Entry::fetch) 45 35 .collect() 46 - .await; 36 + .await; 47 37 48 38 Ok(Output { keys }) 49 39 }
+4 -3
src/lib.rs
··· 1 - pub mod output; 2 1 pub mod config; 2 + pub mod output; 3 + pub mod sources; 3 4 4 - pub fn fuk() { 5 - println!("Fuk"); 5 + pub async fn fuk(config: config::Config) -> output::Output { 6 + config.fetch().await.unwrap() 6 7 }
+21 -1
src/main.rs
··· 1 + const EXAMPLE: &'static str = r#" 2 + [[entry]] 3 + name = "hauleth" 4 + keys = [ 5 + { Forge = { user = "~hauleth", host = "meta.sr.ht" } } 6 + ] 7 + 8 + [[entry]] 9 + name = "heimdall" 10 + keys = [ 11 + { Host = [ "heimdall" ] } 12 + ] 13 + "#; 14 + 1 15 #[tokio::main] 2 16 async fn main() { 3 - fuk::fuk(); 17 + let config: fuk::config::Config = 18 + toml::from_str(EXAMPLE).unwrap(); 19 + 20 + println!("{:#?}", config); 21 + 22 + let output = fuk::fuk(config).await; 23 + println!("{}", serde_json::to_string_pretty(&output.keys).unwrap()); 4 24 }
+1
src/output/mod.rs
··· 1 1 use std::collections::HashMap; 2 2 3 + #[derive(Debug, serde::Serialize)] 3 4 pub struct Output { 4 5 pub keys: HashMap<String, Vec<String>>, 5 6 }
+76
src/sources/mod.rs
··· 1 + use tokio::process::Command; 2 + 3 + use serde::Deserialize; 4 + use async_trait::async_trait; 5 + 6 + #[typetag::deserialize] 7 + #[async_trait] 8 + pub trait Fetch: std::fmt::Debug { 9 + async fn fetch(&self) -> Vec<String>; 10 + } 11 + 12 + #[derive(Debug, Deserialize)] 13 + pub struct Raw(Vec<String>); 14 + 15 + #[typetag::deserialize] 16 + #[async_trait::async_trait] 17 + impl Fetch for Raw { 18 + async fn fetch(&self) -> Vec<String> { 19 + self.0.clone() 20 + } 21 + } 22 + 23 + #[derive(Debug, Deserialize)] 24 + pub struct Host(pub Vec<String>); 25 + 26 + #[typetag::deserialize] 27 + #[async_trait] 28 + impl Fetch for Host { 29 + async fn fetch(&self) -> Vec<String> { 30 + let result = Command::new("ssh-keyscan") 31 + .args(&self.0) 32 + .output() 33 + .await 34 + .unwrap(); 35 + 36 + std::str::from_utf8(&result.stdout) 37 + .unwrap() 38 + .trim() 39 + .split('\n') 40 + .map(|line| { 41 + // Ignore first column as it contain hostname which is not 42 + // needed there 43 + line.split(' ') 44 + .skip(1) 45 + .collect::<Vec<_>>() 46 + .join(" ") 47 + .to_owned() 48 + }) 49 + .collect() 50 + } 51 + } 52 + 53 + #[derive(Debug, Deserialize)] 54 + pub struct Forge { 55 + pub user: String, 56 + pub host: String, 57 + } 58 + 59 + #[typetag::deserialize] 60 + #[async_trait] 61 + impl Fetch for Forge { 62 + async fn fetch(&self) -> Vec<String> { 63 + let url = format!("https://{}/{}.keys", self.host, self.user); 64 + 65 + reqwest::get(url) 66 + .await 67 + .unwrap() 68 + .text() 69 + .await 70 + .unwrap() 71 + .trim() 72 + .split('\n') 73 + .map(|s| s.to_owned()) 74 + .collect() 75 + } 76 + }