Function Causing Unknown Error

⚓ Rust    📅 2026-06-22    👤 surdeus    👁️ 1      

surdeus

I have developed a for-loop with an automatically-incrementing counter, with a TOML-based configuration-file, and defaults for that TOML-based configuration-file for when the file does not exist. Upon running my code, which was working until adding the default parameters, it attempts to create the initialisation-file twice, always failing to create it (and throwing exit-code 1, as coded into that error branch).

My project is split into 3 modules; all of which are shown, below, in their entirety.

I cannot seem to find where the issue is caused. After hours of debugging, I am hoping someone with more experience and/or knowledge can see where the issue lies. Thank you in advance to anyone taking the time to assist with this.

main.rs:

mod config;
mod generate;


fn main() {

	config::config_read();

	generate::generate();

}

config.rs:

use std::fs;
use std::sync::LazyLock;
use serde::Deserialize;


// Master struct containing the blocks of the TOML-based configuration-file.
#[derive(Debug, Default, Deserialize)]
pub struct Config {
	pub cert: Cert,
	pub key: Key,
}

// struct containing the keys of the "cert" block in the TOML-based configuration-file.
#[derive(Debug, Default, Deserialize)]
pub struct Cert {
	#[serde(default = "default_principle")]
	pub principle: String,
	#[serde(default = "default_validity")]
	pub validity: String,
}

// struct containing the keys of the "key" block in the TOML-based configuration-file.
#[derive(Debug, Default, Deserialize)]
pub struct Key {
	#[serde(default = "default_ca_priv_key")]
	pub ca_priv_key: String,
	#[serde(default = "default_user_dir")]
	pub user_dir: String,
	#[serde(default = "default_cert_serial")]
	pub cert_serial: String,
}


// Functions containing the logic for the default configuration-file values.
fn default_principle() -> String { "root".to_string() }

fn default_validity() -> String { "+7d".to_string() }

fn default_ca_priv_key() -> String {

	// Get the value of the "$HOME" environment variable.
	let home_env = std::env::var("HOME").unwrap();

	format!("{}/.ssh/id_ed25519", &home_env)

}

fn default_user_dir() -> String {

	// Get the value of the "$HOME" environment variable.
	let home_env = std::env::var("HOME").unwrap();

	format!("{}/.ssh", &home_env)

}

fn default_cert_serial() -> String {

	// Get the value of the "$HOME" environment variable.
	let home_env = std::env::var("HOME").unwrap();

	format!("{}/.ssh/cert-serial.txt", &home_env)

}


// Function containing the logic for reading the TOML-based configuration-file.
pub fn config_read() -> Config {

	// Get the value of the "$HOME" environment variable.
	let home_env = std::env::var("HOME").unwrap();
	// Append the configuration-file path to the "$HOME" environment-variable value.
	let config_file = format!("{}/.config/signatory/config.toml", home_env);
	// Parse the keys inside of the configuration-file and return the result of the parsing to the caller.
	match fs::read_to_string(&config_file) {
		Ok(content) => return toml::from_str(&content).unwrap_or_default(),
		Err(_) => {
			println!("\x1b[1;33mWARN: Configuration-file not found. Using default configuration.\x1b[0m");
			Config::default()
		}

	}

}


// static allowing the configuration-file structs to be referenced in other modules (target: generate.rs).
pub static CONFIG: LazyLock<Config> = LazyLock::new (|| {

	config_read()

});

generate.rs:

use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::path::Path;
use std::process::Command;
use std::process;
use glob::glob;

#[path = "config.rs"]
mod config;


// Function containing the logic for checking whether the certificate-serial-file exists.
fn cert_serial_file_init(path: &str) -> io::Result<String> {

	// If the certificate-serial-file exists, open it. If it does not exist, create it, then open it.
	if !Path::new(path).exists() {
		println!("\x1b[1;33mWARN: Certificate-serial-file does not exist. Creating certificate-serial-file...\x1b[0m");
		File::create(path)
			.map_err(|e| {
				eprintln!("\x1b[1;31mERROR (1): Failed to create certificate-serial-file!");
				process::exit(1);
			});
		println!("\x1b[1;32mSuccessfully created certificate-serial-file.\x1b[0m");

	}

	// If the certificate-serial-file contains a string, return that string. If it does not contain a string,
	// zero-initialise the string.
	let mut content = fs::read_to_string(path).unwrap_or_default();
	if content.trim().is_empty() {
		println!("\x1b[1;33mWARN: Certificate-serial-file empty. Zero-initialising certificate-serial-file...\x1b[0m");
		fs::write(path, "0")
			.map_err(|e| {
				eprintln!("\x1b[1;31mERROR (2): Failed to zero-initialise certificate-serial-file!");
				process::exit(2);
			});
		println!("\x1b[1;32mSuccessfully zero-initialised certificate-serial-file.\x1b[0m");
		content = "0".to_string()
	}
	Ok(content)

}


// Function containing the logic for stripping the path and filename-extension from OpenSSH public-keys, in order to use
// the result as the public-key principle.
fn strip_ext(filename: &str) -> &str {

	filename.rsplit('/').next().unwrap_or(filename).split('.').next().unwrap_or_else(|| {
		eprintln!("\x1b[1;31mERROR (3): Failed to strip path and filename extension from public-key!\x1b[0m");
		process::exit(3);
	})

}


// Function containing the logic for the serial-number incrementation after each OpenSSH-certificate generation.
fn increment(n: &mut u32) {

	*n += 1

}


// Function containing the logic for the OpenSSH-certificate generation.
pub fn generate() {

	// Variable containing the passed result of the configuration-file structs (for more information, consult
	// config.rs).
	let config = &*crate::config::CONFIG;

	// Variable containing the user OpenSSH public-keys, by appending the public-key filename extension(s) to the user
	// OpenSSH public-key directory.
	let mut user_dir_pubkey = [&config.key.user_dir, "/*.pub"].join("");

	// Check whether the certificate-serial-file exists and contains a string to use as the OpenSSH-certificate serial
	// number.
	cert_serial_file_init(&config.key.cert_serial);

	// Iterate through the defined user OpenSSH public-keys and generate respective OpenSSH certificates for them.
	//
	// The certificate-serial-file is read before each certificate generation, in order to use the value as the serial
	// number for the next certificate.
	for user in glob(&user_dir_pubkey).unwrap_or_else(|_| {
		eprintln!("\x1b[1;31mERROR (4): Failed to read glob pattern!\x1b[0m");
		process::exit(4);
	}) {

		// Variable containing the contents of the certificate-serial-file.
		let mut serial = fs::read_to_string(&config.key.cert_serial).expect("ERROR: Failed to parse initial serial number!");
		let mut serial_int: u32 = serial.trim().parse::<u32>().expect("ERROR: Failed to parse initial serial number!");

		let pub_key = user.unwrap_or_else(|_| {
			eprintln!("\x1b[1;31mERROR (5): Failed to read glob result!\x1b[0m");
			process::exit(5);
		});

		// Check if the user OpenSSH public-key is valid via checking whether the filename can be read, it uses UTF-8
		// character-encoding, and it is not a currently-existing OpenSSH-certificate-file.
		if pub_key
			.file_name()
			.unwrap_or_else(|| {
				eprintln!("\x1b[1;31mERROR (6): Failed to read filename!\x1b[0m");
				process::exit(6);
			})
			.to_str()
			.unwrap_or_else(|| {
				eprintln!("\x1b[1;31mERROR (7): Detected non-UTF-8 filename!\x1b[0m");
				process::exit(7);
			})
			.ends_with("-cert.pub") {
				continue
		}

		// Strip the filename extension from the public-key filename, in order to use it as the OpenSSH public-key
		// principle.
		let pub_key_str: &str = &pub_key.display().to_string();
		let pub_key_principle: &str = strip_ext(&pub_key_str);

		// Begin generating the OpenSSH certificates.
		println!("Generating OpenSSH certificate for {}...", &pub_key_principle);
		let cert_gen = Command::new("ssh-keygen")
			.args([
				// OpenSSH certificate-authority private-key.
				"-s", &config.key.ca_priv_key,
				// OpenSSH public-key principle.
				"-I", &pub_key_principle,
				// OpenSSH remote principle(s).
				"-n", &config.cert.principle,
				// OpenSSH-certificate validity timeframe.
				"-V", &["".to_string(), config.cert.validity.to_string(), "".to_string()].join(""),
				// Clear default OpenSSH-certificate permissions.
				"-O", "clear",
				// Add pty permission to OpenSSH certificate.
				"-O", "permit-pty",
				// Inject the contents of the certificate-serial-file as the certificate serial number.
				"-z", &serial_int.to_string(),
				// OpenSSH public-key file to sign.
				&pub_key.display().to_string(),
			])
			.status()
			.unwrap_or_else(|_| {
				eprintln!("\x1b[1;31mERROR (8): Failed to generate OpenSSH certificate for {}!\x1b[0m",
					&pub_key.display().to_string());
				process::exit(8);
			});
		// Convert the string read from the certificate-serial-file to an integer to allow it to be incremented as a
		// number.
		let mut serial_int: u32 = serial.trim().parse().expect("ERROR: Failed to convert serial-number string to integer!");
		increment(&mut serial_int);
		fs::write(&config.key.cert_serial, &serial_int.to_string());

	}


}

4 posts - 2 participants

Read full topic

🏷️ Rust_feed