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

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

View File

@@ -1,3 +1,4 @@
pub mod node;
pub mod carrier;
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 route;
pub mod util;
mod cluster;
pub mod cluster;
pub mod entity;
pub use db::Db;
pub mod services;

View File

@@ -2,10 +2,12 @@ use rocket::fairing::AdHoc;
mod volume;
mod node;
mod system;
pub(super) fn init() -> AdHoc {
AdHoc::on_ignite("Registering routes", |rocket| async {
rocket.attach(volume::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 log::{error, info};
use std::{env, process};
use crate::api::cluster::system::ensure_ssh_keypair;
use crate::api::util::read_p5x_config;
fn configure_rocket() -> Rocket<Build> {
@@ -23,6 +24,8 @@ async fn main() {
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
info!(target: "p5x", "Successfully read config from environment.");
info!(target: "p5x", "Cluster host: {} ({})", config.pve_host_name, config.pve_api_host);