commit 430ff3d330718cb4f1b5c07b7d9345ed9d0714fb Author: Tobias Schneider Date: Mon Aug 22 02:48:04 2022 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67e5be9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/config.yaml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e6a60ab --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,363 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "libssh2-sys" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50845f68d5c693aac7d72a25415ddd21cb8182c04eafe447b73af55a05f9e1b" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "ssh2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269343e64430067a14937ae0e3c4ec604c178fb896dde0964b1acd22b3e2eeb1" +dependencies = [ + "bitflags", + "libc", + "libssh2-sys", + "parking_lot", +] + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync-ssh-keys" +version = "0.1.0" +dependencies = [ + "dirs", + "serde", + "serde_yaml", + "ssh2", +] + +[[package]] +name = "thiserror" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..95ab743 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sync-ssh-keys" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dirs = "4.0.0" +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9.9" +ssh2 = "0.9" diff --git a/config-example.yaml b/config-example.yaml new file mode 100644 index 0000000..4933e57 --- /dev/null +++ b/config-example.yaml @@ -0,0 +1,26 @@ +keys: + john-doe: ssh-rsa XXXXXXXXX + jane-doe: ssh-rsa YYYYYYYYY + +groups: + admin: + - john-doe + marketing: + - jane-doe + +hosts: + - host: google.com + users: + root: + groups: + - admin + other-ssh-user: + groups: + - marketing + keys: + - john-doe + - host: amazon.com + users: + root: + groups: + - admin diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5e33c81 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,29 @@ +use std::{collections::HashMap, fs, path::Path}; + +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Config { + pub keys: HashMap, + pub groups: HashMap>, + pub hosts: Vec +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Host { + pub host: String, + pub port: Option, + pub users: HashMap< + String, + HashMap< + String, + Vec + > + > +} + +pub fn read() -> Config { + let yaml = fs::read_to_string(Path::new("config.yaml")).unwrap(); + let config: Config = serde_yaml::from_str(&yaml).unwrap(); + return config; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c00dffe --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +mod config; + +use std::{net::TcpStream, path::{Path, PathBuf}, io::Write}; +use ssh2::Session; + +fn upload_file(host: &str, port: u16, username: &str, private_key: &Path, passphrase: Option<&str>, content: &str) -> Result<(), Box> { + let tcp: TcpStream = TcpStream::connect(format!("{}:{}", host, port))?; + let mut sess: Session = Session::new()?; + + sess.set_timeout(3000); + sess.set_tcp_stream(tcp); + sess.handshake()?; + + let public_key: PathBuf = private_key.with_extension("pub"); + + sess.userauth_pubkey_file(&username, Some(&public_key), &private_key, passphrase)?; + + let mut remote_file = sess.scp_send(Path::new(".ssh").join("authorized_keys2").as_path(), 0o644, content.len() as u64, None)?; + + remote_file.write(content.as_bytes())?; + + remote_file.send_eof()?; + remote_file.wait_eof()?; + remote_file.close()?; + remote_file.wait_close()?; + + return Ok(()); +} + +fn generate_authorized_keys(host_keys: Vec) -> String { + return format!("###\n# Warning this file has been generated and will be overwritten!\n###\n\n{}\n", host_keys.join("\n")); +} + +fn main() { + let ssh_dir: PathBuf = dirs::home_dir().unwrap().join(".ssh"); + let private_key: PathBuf = ssh_dir.join("id_ed25519_old"); + let passphrase: Option<&str> = None; + let config = config::read(); + + for host in &config.hosts { + for (user_name, user_data) in &host.users { + let mut host_keys: Vec = [].to_vec(); + + if user_data.contains_key("groups") { + for group in user_data["groups"].as_slice() { + if !config.groups.contains_key(group) { + println!("WARNING: Key-group \"{}\" not fount!", group); + continue; + } + for key_name in config.groups[group].as_slice() { + if config.keys.contains_key(key_name) { + host_keys.push(config.keys.get(key_name).unwrap().to_string()) + } + } + } + } + + if user_data.contains_key("keys") { + for key_name in user_data["keys"].as_slice() { + if !config.keys.contains_key(key_name) { + println!("WARNING: Key \"{}\" not found!", key_name); + continue; + } + host_keys.push(config.keys.get(key_name).unwrap().to_string()) + } + } + + // Remove duplicates + host_keys.sort_unstable(); + host_keys.dedup(); + + if host_keys.is_empty() { + continue; + } + + let mut port: u16 = 22; + + if host.port.is_some() { + port = host.port.unwrap(); + } + + let content = &generate_authorized_keys(host_keys); + + // TODO: Make async! TOKIO!!! + match upload_file(&host.host, port, user_name, &private_key.as_path(), passphrase, content) { + Ok(_) => println!("✅ {}@{}", user_name, host.host), + Err(_) => println!("❌ {}@{}", user_name, host.host) + } + } + } +}