Seperated code into different files and functons Part 1

Dieser Commit ist enthalten in:
Sebastian Tobie 2025-05-14 23:04:56 +02:00
Ursprung daff7b814f
Commit 64a2c7aa13
9 geänderte Dateien mit 339 neuen und 253 gelöschten Zeilen

Datei anzeigen

@ -3,20 +3,25 @@
#![allow(clippy::clone_on_copy)]
#![allow(clippy::identity_op)]
pub(crate) mod config;
pub(crate) mod consts;
pub(crate) mod macros;
pub(crate) mod process;
pub(crate) mod structs;
pub(crate) mod types;
pub(crate) mod utils;
use crate::{
config::SiteConfig,
consts::*,
macros::match_error,
structs::{
Arguments,
ProcessorArgs,
types::{
config,
config::{
CA,
SiteConfig,
},
structs::{
Arguments,
ProcessorArgs,
},
},
utils::prefix_emails,
};
@ -28,7 +33,6 @@ use acme2_eab::{
};
use async_scoped::TokioScope;
use clap::Parser;
use config::CA;
use env_logger::init as log_init;
use libsystemd::daemon;
use log::*;
@ -39,7 +43,10 @@ use openssl::{
Private,
},
};
use process::process_site;
use process::{
process_site,
services,
};
use reqwest::{
Client,
tls::Version,
@ -72,9 +79,10 @@ use tokio_stream::{
StreamExt,
wrappers::ReadDirStream,
};
use zbus_systemd::systemd1;
type SafeSet<T> = Mutex<HashSet<T>>;
use types::{
config::GeneralConfig,
traits::FromFile as _,
};
fn default_client() -> reqwest::Client {
@ -201,13 +209,12 @@ async fn process_accounts(
async fn racme(flags: Arguments) {
let client = default_client();
let systemd_access = daemon::booted();
let mainconfig =
config::read_config::<config::GeneralConfig>(match_error!(FILE_MODE.open(flags.config).await=>Err(error)-> "error reading the config: {error}")).await;
let mainconfig = GeneralConfig::from_file(match_error!(FILE_MODE.open(flags.config).await=>Err(error)-> "error reading the config: {error}")).await;
trace!("Parsed Config: {mainconfig:?}");
let files = ReadDirStream::new(match_error!(read_dir(mainconfig.sites_path.clone()).await=>Err(error)-> "could not read files from sites dir: {error}"));
let mut siteconfigs = Vec::new();
for file in files.filter(Result::is_ok).map(|file| file.unwrap().path()).collect::<Vec<PathBuf>>().await {
let mut site = config::read_config::<SiteConfig>(FILE_MODE.open(file.clone()).await.unwrap()).await;
let mut site = SiteConfig::from_file(FILE_MODE.open(file.clone()).await.unwrap()).await;
site.name = file.file_stem().unwrap().to_str().unwrap().to_string();
siteconfigs.push(site);
}
@ -262,24 +269,7 @@ async fn racme(flags: Arguments) {
.await;
if systemd_access {
let conn = match_error!(zbus_systemd::zbus::Connection::system().await=>Err(error)-> "Failed to connect with the systemd manager: {error}");
let systemd_manager = systemd1::ManagerProxy::new(&conn).await.unwrap();
let restart_services = restart_services.into_inner();
for service in reload_services.into_inner().difference(&restart_services.clone()) {
match systemd_manager.reload_unit(service.to_owned(), "replace".to_string()).await {
Ok(_) => info!("Reloaded {service}"),
Err(error) => error!("Failed to reload service {service}: {error}"),
};
}
for service in restart_services.iter() {
match systemd_manager.restart_unit(service.to_owned(), "replace".to_string()).await {
Ok(_) => info!("Restarted {service}"),
Err(error) => error!("Failed to restart service {service}: {error}"),
};
}
services(restart_services.into_inner(), reload_services.into_inner()).await;
}
}

Datei anzeigen

@ -1,14 +1,11 @@
use std::{
collections::HashSet,
fs::Permissions,
os::unix::fs::PermissionsExt,
path::PathBuf,
};
use crate::{
config::{
Algorithm,
match_algo,
},
consts::{
ATTEMPTS,
FILE_MODE,
@ -16,15 +13,19 @@ use crate::{
WAIT_TIME,
},
load_privkey,
match_error,
structs::{
ProcessorArgs,
San,
},
utils::{
gen_key,
is_matching,
macros::match_error,
types::{
cryptography::Algorithm,
structs::{
ProcessorArgs,
San,
},
traits::{
MatchAlgorithm as _,
MatchX509,
},
},
utils::gen_key,
};
use acme2_eab::{
Authorization,
@ -62,6 +63,7 @@ use tokio::{
AsyncWriteExt,
},
};
use zbus_systemd::systemd1;
fn gen_stack(args: &ProcessorArgs, context: X509v3Context) -> Stack<X509Extension> {
let mut stack = Stack::new().unwrap();
@ -104,7 +106,7 @@ pub async fn process_site(args: ProcessorArgs<'_>) {
private_key = match_error!(gen_key(args.algorithm(), args.strength())=>Err(error)-> "Aborting processing the site due to problem with the certificate generation: {error}");
} else if let Ok(key) = load_privkey(private_key_file.clone()).await {
private_key = key;
if !match_algo(&private_key, args.algorithm(), args.strength()) {
if !private_key.matches(args.algorithm(), args.strength()) {
info!("Algorithm for the private key has changed, updating the key");
cert_renew = true;
write_pkey = true;
@ -133,7 +135,10 @@ pub async fn process_site(args: ProcessorArgs<'_>) {
Ok(key) => key,
Err(_) => todo!(),
};
if !is_matching(pubkey, args.refresh_time(), args.san()) {
if !pubkey.days_left(args.refresh_time()) {
info!("Certificate is running out of time");
cert_renew = true
} else if !pubkey.match_san(args.san()) {
info!("Subject Alternative Names differ from Certifcate");
cert_renew = true;
};
@ -249,3 +254,23 @@ pub async fn process_auth(auth: Authorization, challenge_dir: Option<PathBuf>, d
}
error!("Cannot prove the challenges: {}", auth.challenges.iter().map(|c| c.r#type.clone()).collect::<Vec<_>>().join(", "))
}
pub async fn services(restart_services: HashSet<String>, reload_services: HashSet<String>) {
let conn = match_error!(zbus_systemd::zbus::Connection::system().await=>Err(error)-> "Failed to connect with the systemd manager: {error}");
let systemd_manager = systemd1::ManagerProxy::new(&conn).await.unwrap();
for service in reload_services.difference(&restart_services.clone()) {
match systemd_manager.reload_unit(service.to_owned(), "replace".to_string()).await {
Ok(_) => info!("Reloaded {service}"),
Err(error) => error!("Failed to reload service {service}: {error}"),
};
}
for service in restart_services.iter() {
match systemd_manager.restart_unit(service.to_owned(), "replace".to_string()).await {
Ok(_) => info!("Restarted {service}"),
Err(error) => error!("Failed to restart service {service}: {error}"),
};
}
}

Datei anzeigen

@ -1,37 +1,28 @@
use crate::{
consts::{
BRAINPOOL_MIDDLE,
BRAINPOOL_STRONG,
BRAINPOOL_WEAK,
SECP_MIDDLE,
SECP_STRONG,
SECP_WEAK,
macros::{
DefDer,
match_error,
},
types::{
VString,
cryptography::{
Algorithm,
Strength,
},
structs::Error,
},
macros::DefDer,
match_error,
structs::Error,
};
use log::*;
use macro_rules_attribute::macro_rules_derive;
use openssl::pkey::{
Id,
PKey,
Private,
};
use serde::{
Deserialize,
de::DeserializeOwned,
};
use serde::Deserialize;
use std::{
collections::HashMap,
net::IpAddr,
};
use tokio::{
fs::File,
io::AsyncReadExt,
};
type VString = Vec<String>;
#[macro_rules_derive(DefDer)]
#[derive(Deserialize)]
@ -50,48 +41,34 @@ pub struct GeneralConfig {
pub ca: HashMap<String, CA>,
}
impl Default for GeneralConfig {
#[inline]
fn default() -> Self {
Self {
accounts_path: Self::default_accounts(),
sites_path: Self::default_sites(),
http_challenge_path: Self::default_challenge(),
dns: Self::default_dns(),
certificates_path: Self::default_certificates(),
ca: Self::default_cas(),
}
}
}
impl GeneralConfig {
#[inline]
fn default_accounts() -> String {
pub(super) fn default_accounts() -> String {
"accounts".into()
}
#[inline]
fn default_sites() -> String {
pub(super) fn default_sites() -> String {
"sites".into()
}
#[inline]
fn default_challenge() -> Option<String> {
pub(super) fn default_challenge() -> Option<String> {
None
}
#[inline]
fn default_dns() -> Option<Dns> {
pub(super) fn default_dns() -> Option<Dns> {
None
}
#[inline]
fn default_cas() -> HashMap<String, CA> {
pub(super) fn default_cas() -> HashMap<String, CA> {
HashMap::new()
}
#[inline]
fn default_certificates() -> String {
pub(super) fn default_certificates() -> String {
"certificates".into()
}
}
@ -143,52 +120,6 @@ impl CA {
}
}
#[macro_rules_derive(DefDer)]
#[derive(Copy, Deserialize, Default)]
pub enum Algorithm {
Rsa,
Brainpool,
Secp,
#[default]
ED25519,
}
#[macro_rules_derive(DefDer)]
#[derive(Copy, Deserialize, Default)]
pub enum Strength {
Weak,
Middle,
#[default]
Strong,
}
impl Strength {
pub fn rsabits(self) -> u32 {
self as u32
}
}
pub fn match_algo(key: &PKey<Private>, algorithm: Algorithm, strength: Strength) -> bool {
match (key.id(), algorithm) {
(Id::ED25519, Algorithm::ED25519) => true,
(Id::RSA, Algorithm::Rsa) if key.bits() == strength.rsabits() => true,
(Id::EC, Algorithm::Secp) | (Id::EC, Algorithm::Brainpool) => {
let pkey = key.ec_key().unwrap();
let curve = pkey.group().curve_name().unwrap();
match (algorithm, strength) {
(Algorithm::Secp, Strength::Weak) if SECP_WEAK == curve => true,
(Algorithm::Secp, Strength::Middle) if SECP_MIDDLE == curve => true,
(Algorithm::Secp, Strength::Strong) if SECP_STRONG == curve => true,
(Algorithm::Brainpool, Strength::Weak) if BRAINPOOL_WEAK == curve => true,
(Algorithm::Brainpool, Strength::Middle) if BRAINPOOL_MIDDLE == curve => true,
(Algorithm::Brainpool, Strength::Strong) if BRAINPOOL_STRONG == curve => true,
_ => false,
}
},
_ => false,
}
}
#[macro_rules_derive(DefDer)]
#[derive(Deserialize, Default)]
pub struct SiteConfig {
@ -229,25 +160,3 @@ pub struct SiteConfig {
#[serde(skip)]
pub name: String,
}
pub async fn read_config<T: Default + DeserializeOwned>(mut file: File) -> T {
let mut data = String::new();
match file.read_to_string(&mut data).await {
Ok(_) => {},
Err(error) => {
warn!("Failed to load config: {error}");
return Default::default();
},
}
match toml::from_str(&data) {
Ok(output) => output,
Err(error) => {
warn!("Failed to parse toml file: {error}");
Default::default()
},
}
}

31
src/types/cryptography.rs Normale Datei
Datei anzeigen

@ -0,0 +1,31 @@
use macro_rules_attribute::macro_rules_derive;
use serde::Deserialize;
use crate::macros::DefDer;
#[macro_rules_derive(DefDer)]
#[derive(Copy, Deserialize, Default)]
pub enum Algorithm {
Rsa,
Brainpool,
Secp,
#[default]
ED25519,
}
#[macro_rules_derive(DefDer)]
#[derive(Copy, Deserialize, Default)]
pub enum Strength {
Weak,
Middle,
#[default]
Strong,
}
impl Strength {
pub fn rsabits(self) -> u32 {
self as u32
}
}

83
src/types/foreign_impl.rs Normale Datei
Datei anzeigen

@ -0,0 +1,83 @@
use std::{
fmt::Display,
net::IpAddr,
};
use acme2_eab::Identifier;
use openssl::x509::GeneralName;
use super::{
config::GeneralConfig,
structs::{
Error,
San,
},
};
impl Default for GeneralConfig {
#[inline]
fn default() -> Self {
Self {
accounts_path: Self::default_accounts(),
sites_path: Self::default_sites(),
http_challenge_path: Self::default_challenge(),
dns: Self::default_dns(),
certificates_path: Self::default_certificates(),
ca: Self::default_cas(),
}
}
}
impl From<GeneralName> for San {
fn from(value: GeneralName) -> Self {
if let Some(dns) = value.dnsname() {
return Self::Dns(dns.to_owned());
}
if let Some(ipaddr) = value.ipaddress() {
if ipaddr.len() == 4 {
let mut addr = [0u8; 4];
addr.copy_from_slice(ipaddr);
return Self::IPAddress(IpAddr::from(addr));
} else {
let mut addr = [0u8; 16];
addr.copy_from_slice(ipaddr);
return Self::IPAddress(IpAddr::from(addr));
}
}
if let Some(email) = value.email() {
return Self::Email(email.to_owned());
}
unreachable!();
}
}
impl From<San> for Identifier {
fn from(value: San) -> Self {
match value {
San::Dns(domain) => {
Identifier {
r#type: "dns".into(),
value: domain,
}
},
San::Email(email) => {
Identifier {
r#type: "email".into(),
value: email,
}
},
San::IPAddress(ip) => {
Identifier {
r#type: "ip".into(),
value: ip.to_string(),
}
},
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}

14
src/types/mod.rs Normale Datei
Datei anzeigen

@ -0,0 +1,14 @@
use std::collections::HashSet;
use tokio::sync::Mutex;
pub mod config;
pub mod cryptography;
mod foreign_impl;
pub mod structs;
pub mod traits;
/// Alias for Vec\<String\>
pub type VString = Vec<String>;
/// Alias for an Safe Hashset
pub type SafeSet<T> = Mutex<HashSet<T>>;

Datei anzeigen

@ -1,32 +1,29 @@
use std::{
collections::HashSet,
fmt::Display,
net::IpAddr,
path::PathBuf,
sync::Arc,
};
use acme2_eab::{
Account,
Identifier,
};
use acme2_eab::Account;
use clap::Parser;
use macro_rules_attribute::macro_rules_derive;
use openssl::x509::GeneralName;
use tokio::sync::MutexGuard;
use crate::{
SafeSet,
config::{
Algorithm,
SiteConfig,
Strength,
},
config::SiteConfig,
macros::{
DefDer,
Hashable,
attr_function,
},
types::{
SafeSet,
cryptography::{
Algorithm,
Strength,
},
},
};
@ -129,56 +126,8 @@ pub enum San {
IPAddress(IpAddr),
}
impl From<GeneralName> for San {
fn from(value: GeneralName) -> Self {
if let Some(dns) = value.dnsname() {
return Self::Dns(dns.to_owned());
}
if let Some(ipaddr) = value.ipaddress() {
if ipaddr.len() == 4 {
let mut addr = [0u8; 4];
addr.copy_from_slice(ipaddr);
return Self::IPAddress(IpAddr::from(addr));
} else {
let mut addr = [0u8; 16];
addr.copy_from_slice(ipaddr);
return Self::IPAddress(IpAddr::from(addr));
}
}
if let Some(email) = value.email() {
return Self::Email(email.to_owned());
}
unreachable!();
}
}
impl From<San> for Identifier {
fn from(value: San) -> Self {
match value {
San::Dns(domain) => {
Identifier {
r#type: "dns".into(),
value: domain,
}
},
San::Email(email) => {
Identifier {
r#type: "email".into(),
value: email,
}
},
San::IPAddress(ip) => {
Identifier {
r#type: "ip".into(),
value: ip.to_string(),
}
},
}
}
}
#[macro_rules_derive(DefDer)]
pub struct Error(String);
pub struct Error(pub(super) String);
impl Error {
#[inline]
@ -191,9 +140,3 @@ impl Error {
Self(message)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}

117
src/types/traits.rs Normale Datei
Datei anzeigen

@ -0,0 +1,117 @@
use std::collections::HashSet;
use crate::consts::{
BRAINPOOL_MIDDLE,
BRAINPOOL_STRONG,
BRAINPOOL_WEAK,
SECP_MIDDLE,
SECP_STRONG,
SECP_WEAK,
};
use log::*;
use openssl::{
asn1::Asn1Time,
pkey::{
Id,
PKey,
Private,
},
x509::X509,
};
use serde::de::DeserializeOwned;
use tokio::{
fs::File,
io::AsyncReadExt,
};
use super::{
cryptography::{
Algorithm,
Strength,
},
structs::San,
};
pub trait FromFile: Default + DeserializeOwned {
async fn from_file(file: File) -> Self;
}
impl<T: Default + DeserializeOwned> FromFile for T {
async fn from_file(mut file: File) -> Self {
let mut data = String::new();
match file.read_to_string(&mut data).await {
Ok(_) => {},
Err(error) => {
warn!("Failed to load config: {error}");
return Default::default();
},
}
match toml::from_str(&data) {
Ok(output) => output,
Err(error) => {
warn!("Failed to parse toml file: {error}");
Default::default()
},
}
}
}
pub trait MatchAlgorithm {
fn matches(&self, algorithm: Algorithm, strength: Strength) -> bool;
}
impl MatchAlgorithm for PKey<Private> {
fn matches(&self, algorithm: Algorithm, strength: Strength) -> bool {
match (self.id(), algorithm) {
(Id::ED25519, Algorithm::ED25519) => true,
(Id::RSA, Algorithm::Rsa) if self.bits() == strength.rsabits() => true,
(Id::EC, Algorithm::Secp) | (Id::EC, Algorithm::Brainpool) => {
let pkey = self.ec_key().unwrap();
let curve = pkey.group().curve_name().unwrap();
match (algorithm, strength) {
(Algorithm::Secp, Strength::Weak) if SECP_WEAK == curve => true,
(Algorithm::Secp, Strength::Middle) if SECP_MIDDLE == curve => true,
(Algorithm::Secp, Strength::Strong) if SECP_STRONG == curve => true,
(Algorithm::Brainpool, Strength::Weak) if BRAINPOOL_WEAK == curve => true,
(Algorithm::Brainpool, Strength::Middle) if BRAINPOOL_MIDDLE == curve => true,
(Algorithm::Brainpool, Strength::Strong) if BRAINPOOL_STRONG == curve => true,
_ => false,
}
},
_ => false,
}
}
}
pub trait MatchX509 {
fn days_left(&self, days: u32) -> bool;
fn match_san<T: IntoIterator<Item = San>>(&self, names: T) -> bool;
}
impl MatchX509 for X509 {
fn days_left(&self, days: u32) -> bool {
let future_date = Asn1Time::days_from_now(days).unwrap();
self.not_after().compare(&future_date).is_ok_and(|order| order.is_le())
}
fn match_san<T: IntoIterator<Item = San>>(&self, names: T) -> bool {
let alt_names = match self.subject_alt_names() {
None => return false,
Some(x) => x,
};
let mut cert_san = HashSet::<San>::new();
for san in alt_names {
cert_san.insert(san.into());
}
let mut config_san = HashSet::new();
for san in names {
config_san.insert(san);
}
config_san.difference(&cert_san).count() == 0
}
}

Datei anzeigen

@ -1,10 +1,4 @@
use std::collections::HashSet;
use crate::{
config::{
Algorithm,
Strength,
},
consts::{
BRAINPOOL_MIDDLE,
BRAINPOOL_STRONG,
@ -13,11 +7,13 @@ use crate::{
SECP_STRONG,
SECP_WEAK,
},
structs::San,
types::cryptography::{
Algorithm,
Strength,
},
};
use log::*;
use openssl::{
asn1::Asn1Time,
ec::EcKey,
error::ErrorStack,
nid::Nid,
@ -27,7 +23,6 @@ use openssl::{
},
rsa::Rsa,
x509::{
X509,
X509Name,
X509NameBuilder,
},
@ -94,27 +89,6 @@ pub fn gen_key(algorithm: Algorithm, strength: Strength) -> Result<PKey<Private>
}
}
pub fn is_matching(cert: X509, daydiff: u32, sans: Vec<San>) -> bool {
let now = Asn1Time::days_from_now(daydiff).unwrap();
if cert.not_after().compare(&now).is_ok_and(|order| order.is_le()) {
return false;
}
let alt_names = match cert.subject_alt_names() {
None => return false,
Some(x) => x,
};
let mut cert_san = HashSet::<San>::new();
for san in alt_names {
cert_san.insert(san.into());
}
let mut config_san = HashSet::with_capacity(sans.len());
for san in sans {
config_san.insert(san);
}
config_san.difference(&cert_san).count() == 0
}
pub fn string_to_cn(name: String) -> X509Name {
let mut builder = X509NameBuilder::new().unwrap();
builder.append_entry_by_nid(Nid::COMMONNAME, &name).unwrap();