Generate SSH key on startup if it does not already exist; add API endpoint to get SSH public key

This commit is contained in:
Garrett Mills 2025-02-24 23:36:22 -05:00
parent 0359de37c8
commit 0ce3ba0512
9 changed files with 347 additions and 2 deletions

278
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -209,6 +209,12 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@ -381,6 +387,16 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.20" version = "4.5.20"
@ -517,6 +533,18 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -527,6 +555,32 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version",
"subtle",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.86",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.20.10" version = "0.20.10"
@ -672,6 +726,41 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest",
"elliptic-curve",
"rfc6979",
"signature",
"spki",
]
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
dependencies = [
"curve25519-dalek",
"ed25519",
"sha2",
"subtle",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.13.0" version = "1.13.0"
@ -681,6 +770,25 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"ff",
"generic-array",
"group",
"pkcs8",
"rand_core",
"sec1",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"
@ -757,6 +865,22 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "ff"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"rand_core",
"subtle",
]
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]] [[package]]
name = "figment" name = "figment"
version = "0.10.19" version = "0.10.19"
@ -949,6 +1073,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
"zeroize",
] ]
[[package]] [[package]]
@ -974,6 +1099,17 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core",
"subtle",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.26" version = "0.3.26"
@ -1226,6 +1362,15 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.13" version = "0.1.13"
@ -1657,6 +1802,44 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2",
]
[[package]]
name = "p384"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
dependencies = [
"ecdsa",
"elliptic-curve",
"primeorder",
"sha2",
]
[[package]]
name = "p521"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
dependencies = [
"base16ct",
"ecdsa",
"elliptic-curve",
"primeorder",
"rand_core",
"sha2",
]
[[package]] [[package]]
name = "p5x" name = "p5x"
version = "0.1.0" version = "0.1.0"
@ -1667,12 +1850,14 @@ dependencies = [
"futures", "futures",
"log", "log",
"proxmox-api", "proxmox-api",
"rand",
"rocket", "rocket",
"sea-orm", "sea-orm",
"sea-orm-migration", "sea-orm-migration",
"sea-orm-rocket", "sea-orm-rocket",
"serde", "serde",
"serde_json", "serde_json",
"ssh-key",
"ssh2", "ssh2",
"tokio", "tokio",
"ureq", "ureq",
@ -1831,6 +2016,15 @@ dependencies = [
"zerocopy", "zerocopy",
] ]
[[package]]
name = "primeorder"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
"elliptic-curve",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "3.2.0" version = "3.2.0"
@ -2090,6 +2284,16 @@ dependencies = [
"bytecheck", "bytecheck",
] ]
[[package]]
name = "rfc6979"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
"hmac",
"subtle",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.8" version = "0.17.8"
@ -2230,6 +2434,7 @@ dependencies = [
"pkcs1", "pkcs1",
"pkcs8", "pkcs8",
"rand_core", "rand_core",
"sha2",
"signature", "signature",
"spki", "spki",
"subtle", "subtle",
@ -2258,6 +2463,15 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.38" version = "0.38.38"
@ -2521,6 +2735,20 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array",
"pkcs8",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.11.1" version = "2.11.1"
@ -2544,6 +2772,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "semver"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.214" version = "1.0.214"
@ -2930,6 +3164,48 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "ssh-cipher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f"
dependencies = [
"cipher",
"ssh-encoding",
]
[[package]]
name = "ssh-encoding"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15"
dependencies = [
"base64ct",
"pem-rfc7468",
"sha2",
]
[[package]]
name = "ssh-key"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3"
dependencies = [
"ed25519-dalek",
"p256",
"p384",
"p521",
"rand_core",
"rsa",
"sec1",
"sha2",
"signature",
"ssh-cipher",
"ssh-encoding",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "ssh2" name = "ssh2"
version = "0.9.4" version = "0.9.4"

View File

@ -20,3 +20,5 @@ serde_json = "1.0.132"
proxmox-api = { git = "https://github.com/glmdev/p5x-proxmox-api", version = "0.1.2-pre", features = ["ureq-client"] } proxmox-api = { git = "https://github.com/glmdev/p5x-proxmox-api", version = "0.1.2-pre", features = ["ureq-client"] }
ureq = "2.10.1" ureq = "2.10.1"
dotenv = "0.15.0" dotenv = "0.15.0"
ssh-key = { version = "0.6.7", features = ["ed25519"] }
rand = "0.8.5"

4
requests/system.http Normal file
View File

@ -0,0 +1,4 @@
GET http://localhost:3450/system/pubkey
Accept: text/plain
###

View File

@ -1,3 +1,4 @@
pub mod node; pub mod node;
pub mod carrier; pub mod carrier;
pub mod volume; pub mod volume;
pub mod system;

42
src/api/cluster/system.rs Normal file
View File

@ -0,0 +1,42 @@
use std::env;
use std::fs::{File, Permissions};
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use log::info;
use rand::rngs::OsRng;
use ssh_key::{PrivateKey, Algorithm, LineEnding};
/** Check if the SSH pubkey/privkey exist at the configured paths. If not, generate them. */
pub fn ensure_ssh_keypair() -> Result<(), ssh_key::Error> {
let pubkey_path = env::var("P5X_SSH_PUBKEY_PATH").expect("Missing env: P5X_SSH_PUBKEY_PATH");
let privkey_path = env::var("P5X_SSH_PRIVKEY_PATH").expect("Missing env: P5X_SSH_PRIVKEY_PATH");
// If both exist, then p5x will boot correctly when it reads from the files.
// If only one of the two files exists, p5x will error on boot. This is safer
// than accidentally overwriting someone's key.
if Path::new(&pubkey_path).exists() || Path::new(&privkey_path).exists() {
info!(target: "p5x", "Found existing SSH keypair on filesystem");
return Ok(());
}
// Generate an ed25519 keypair
info!(target: "p5x", "Generating a new SSH keypair");
let mut csprng = OsRng;
let privkey = PrivateKey::random(&mut csprng, Algorithm::Ed25519)?;
// Write the privkey to a file
let privkey_pem = privkey.to_openssh(LineEnding::LF)?;
let mut privkey_file = File::create(privkey_path)?;
privkey_file.write_all(privkey_pem.as_bytes())?;
privkey_file.set_permissions(Permissions::from_mode(0o600))?;
// Write the pubkey to a file
let pubkey = privkey.public_key();
let pubkey_ssh = pubkey.to_openssh()?;
let mut pubkey_file = File::create(pubkey_path)?;
pubkey_file.write_all(pubkey_ssh.as_bytes())?;
Ok(())
}

View File

@ -3,7 +3,7 @@ use rocket::fairing::AdHoc;
mod db; mod db;
mod route; mod route;
pub mod util; pub mod util;
mod cluster; pub mod cluster;
pub mod entity; pub mod entity;
pub use db::Db; pub use db::Db;
pub mod services; pub mod services;

View File

@ -2,10 +2,12 @@ use rocket::fairing::AdHoc;
mod volume; mod volume;
mod node; mod node;
mod system;
pub(super) fn init() -> AdHoc { pub(super) fn init() -> AdHoc {
AdHoc::on_ignite("Registering routes", |rocket| async { AdHoc::on_ignite("Registering routes", |rocket| async {
rocket.attach(volume::init()) rocket.attach(volume::init())
.attach(node::init()) .attach(node::init())
.attach(system::init())
}) })
} }

15
src/api/route/system.rs Normal file
View File

@ -0,0 +1,15 @@
use rocket::fairing::AdHoc;
use rocket::response::status;
use crate::api::util::read_p5x_config;
#[get("/pubkey")]
fn get_pubkey() -> Result<String, status::Custom<String>> {
let config = read_p5x_config();
Ok(config.ssh_pubkey)
}
pub(super) fn init() -> AdHoc {
AdHoc::on_ignite("Routes: /system", |rocket| async {
rocket.mount("/system", routes![get_pubkey])
})
}

View File

@ -4,6 +4,7 @@ use dotenv::dotenv;
use rocket::{Build, Rocket}; use rocket::{Build, Rocket};
use log::{error, info}; use log::{error, info};
use std::{env, process}; use std::{env, process};
use crate::api::cluster::system::ensure_ssh_keypair;
use crate::api::util::read_p5x_config; use crate::api::util::read_p5x_config;
fn configure_rocket() -> Rocket<Build> { fn configure_rocket() -> Rocket<Build> {
@ -23,6 +24,8 @@ async fn main() {
process::exit(1); process::exit(1);
} }
ensure_ssh_keypair().expect("Could not ensure SSH keypair exists.");
let config = read_p5x_config(); // Do this so we early-fail if there are missing env vars let config = read_p5x_config(); // Do this so we early-fail if there are missing env vars
info!(target: "p5x", "Successfully read config from environment."); info!(target: "p5x", "Successfully read config from environment.");
info!(target: "p5x", "Cluster host: {} ({})", config.pve_host_name, config.pve_api_host); info!(target: "p5x", "Cluster host: {} ({})", config.pve_host_name, config.pve_api_host);