Fetch User Keys - simple tool for fetching SSH keys from various sources
1// SPDX-FileCopyrightText: 2024 Łukasz Niemier <#@hauleth.dev> 2// 3// SPDX-License-Identifier: EUPL-1.2 4 5use ssh_key::PublicKey; 6use tokio::process::Command; 7use serde::Deserialize; 8use async_trait::async_trait; 9 10#[async_trait] 11pub trait Fetch: std::fmt::Debug { 12 async fn fetch(&self) -> Vec<PublicKey>; 13} 14 15#[derive(Debug, Deserialize)] 16#[serde(rename_all = "lowercase")] 17pub enum Source { 18 Raw(Raw), 19 Hosts(Hosts), 20 Http(Http), 21 Github(String), 22 Sourcehut(String), 23 Gitlab(String), 24 Codeberg(String), 25} 26 27#[async_trait] 28impl Fetch for Source { 29 async fn fetch(&self) -> Vec<PublicKey> { 30 match *self { 31 Source::Raw(ref raw) => raw.fetch().await, 32 Source::Hosts(ref raw) => raw.fetch().await, 33 Source::Http(ref raw) => raw.fetch().await, 34 Source::Github(ref user) => Http { url: format!("https://github.com/{user}.keys") }.fetch().await, 35 Source::Sourcehut(ref user) => Http { url: format!("https://meta.sr.ht/{user}.keys") }.fetch().await, 36 Source::Gitlab(ref user) => Http { url: format!("https://gitlab.com/{user}.keys") }.fetch().await, 37 Source::Codeberg(ref user) => Http { url: format!("https://codeberg.org/{user}.keys") }.fetch().await, 38 } 39 } 40} 41 42#[derive(Debug, Deserialize)] 43pub struct Raw(Vec<PublicKey>); 44 45#[async_trait::async_trait] 46impl Fetch for Raw { 47 async fn fetch(&self) -> Vec<PublicKey> { 48 self.0.clone() 49 } 50} 51 52#[derive(Debug, Deserialize)] 53pub struct Hosts(pub Vec<String>); 54 55#[async_trait] 56impl Fetch for Hosts { 57 async fn fetch(&self) -> Vec<PublicKey> { 58 // TODO: Check if we can do it in-process instead of shelling out to `ssh-keyscan` 59 let result = Command::new("ssh-keyscan") 60 .args(&self.0) 61 .output() 62 .await 63 .unwrap(); 64 65 std::str::from_utf8(&result.stdout) 66 .unwrap() 67 .trim() 68 .split('\n') 69 .map(|line| { 70 // Ignore first column as it contain hostname which is not 71 // needed there 72 line.split(' ') 73 .skip(1) 74 .collect::<Vec<_>>() 75 .join(" ") 76 .to_owned() 77 }) 78 .map(|k| PublicKey::from_openssh(&k).unwrap()) 79 .collect() 80 } 81} 82 83#[derive(Debug, Deserialize)] 84pub struct Http { 85 pub url: String 86} 87 88#[async_trait] 89impl Fetch for Http { 90 async fn fetch(&self) -> Vec<PublicKey> { 91 reqwest::get(&self.url) 92 .await 93 .unwrap() 94 .text() 95 .await 96 .unwrap() 97 .trim() 98 .split('\n') 99 .map(|s| PublicKey::from_openssh(s).unwrap()) 100 .collect() 101 } 102}