Function Causing Unknown Error
⚓ Rust 📅 2026-06-22 👤 surdeus 👁️ 1I 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
🏷️ Rust_feed