some more refactoring
Dieser Commit ist enthalten in:
Ursprung
d982920cf2
Commit
8b9bf2a77d
15 geänderte Dateien mit 325 neuen und 139 gelöschten Zeilen
11
Cargo.lock
generiert
11
Cargo.lock
generiert
|
@ -1543,6 +1543,7 @@ dependencies = [
|
|||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"systemd-journal-logger",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml",
|
||||
|
@ -2016,6 +2017,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "systemd-journal-logger"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7266304d24ca5a4b230545fc558c80e18bd3e1d2eb1be149b6bcd04398d3e79c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
|
|
|
@ -68,6 +68,10 @@ default-features = false
|
|||
features = ["std"]
|
||||
version = "1.0.140"
|
||||
|
||||
[dependencies.systemd-journal-logger]
|
||||
default-features = false
|
||||
version = "2.2.2"
|
||||
|
||||
[dependencies.tokio]
|
||||
default-features = false
|
||||
features = ["rt", "sync", "time", "net", "macros"]
|
||||
|
@ -91,6 +95,7 @@ unstable = ["capabilities"]
|
|||
edition = "2024"
|
||||
name = "racme"
|
||||
resolver = "3"
|
||||
rust-version = "1.87"
|
||||
version = "0.1.0"
|
||||
|
||||
[patch.crates-io.acme2-eab]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "GeneralConfig",
|
||||
"title": "General",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accounts_path": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "SiteConfig",
|
||||
"title": "Site",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ca": {
|
||||
|
|
71
src/main.rs
71
src/main.rs
|
@ -1,9 +1,15 @@
|
|||
//! Acme client that supports multiple CAs and configs for sites that can be seperate from the mainconfig
|
||||
#![allow(clippy::clone_on_copy)]
|
||||
#![allow(clippy::identity_op)]
|
||||
#![allow(refining_impl_trait)]
|
||||
#![allow(clippy::collapsible_if)]
|
||||
#![allow(clippy::identity_op)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(refining_impl_trait)]
|
||||
#![deny(clippy::format_push_string)]
|
||||
#![deny(clippy::macro_use_imports)]
|
||||
#![deny(clippy::module_name_repetitions)]
|
||||
#![deny(clippy::single_component_path_imports)]
|
||||
#![deny(clippy::unnecessary_debug_formatting)]
|
||||
#![deny(clippy::unnecessary_self_imports)]
|
||||
|
||||
pub(crate) mod consts;
|
||||
pub(crate) mod macros;
|
||||
|
@ -17,8 +23,8 @@ use crate::{
|
|||
prelude::*,
|
||||
types::{
|
||||
config::{
|
||||
GeneralConfig,
|
||||
SiteConfig,
|
||||
General,
|
||||
Site,
|
||||
},
|
||||
dns::Manager,
|
||||
structs::{
|
||||
|
@ -28,20 +34,15 @@ use crate::{
|
|||
SubCommand,
|
||||
},
|
||||
},
|
||||
utils::check_permissions,
|
||||
utils::{
|
||||
check_permissions,
|
||||
logging,
|
||||
},
|
||||
};
|
||||
use acme2_eab::Directory;
|
||||
use clap::Parser;
|
||||
use env_logger::init as log_init;
|
||||
use libsystemd::daemon;
|
||||
use log::*;
|
||||
use openssl::{
|
||||
self,
|
||||
pkey::{
|
||||
PKey,
|
||||
Private,
|
||||
},
|
||||
};
|
||||
use reqwest::{
|
||||
Client,
|
||||
tls::Version,
|
||||
|
@ -52,9 +53,12 @@ use schemars::{
|
|||
generate::SchemaSettings,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::ser::{
|
||||
Formatter,
|
||||
PrettyFormatter,
|
||||
use serde_json::{
|
||||
Serializer,
|
||||
ser::{
|
||||
Formatter,
|
||||
PrettyFormatter,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
collections::{
|
||||
|
@ -75,10 +79,8 @@ use tokio::{
|
|||
create_dir_all,
|
||||
read_dir,
|
||||
},
|
||||
io::{
|
||||
AsyncReadExt,
|
||||
AsyncWriteExt,
|
||||
},
|
||||
io::AsyncWriteExt as _,
|
||||
runtime::Builder,
|
||||
sync::Mutex,
|
||||
};
|
||||
use tokio_stream::{
|
||||
|
@ -99,21 +101,6 @@ fn default_client() -> Result<Client, Error> {
|
|||
.map_err(Error::from_display)
|
||||
}
|
||||
|
||||
async fn load_privkey(path: PathBuf) -> Result<PKey<Private>, Error> {
|
||||
let mut file = match FILE_MODE.open(path).await {
|
||||
Ok(file) => file,
|
||||
Err(error) => return Error::err(format!("Failed to open Private Key: {error}")),
|
||||
};
|
||||
let mut data = String::new();
|
||||
if let Err(error) = file.read_to_string(&mut data).await {
|
||||
return Error::err(format!("Failed to read data for the key: {error}"));
|
||||
}
|
||||
match PKey::private_key_from_pem(data.as_bytes()) {
|
||||
Ok(key) => Ok(key),
|
||||
Err(error) => Error::err(format!("Failed to parse pem data: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn racme(flags: Arguments) -> Result<(), Error> {
|
||||
let client = default_client()?;
|
||||
let mut dns_manager = Manager::new(client.clone());
|
||||
|
@ -125,7 +112,7 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
|||
return Error::err(format!("error reading the config: {error}"));
|
||||
},
|
||||
};
|
||||
GeneralConfig::from_file(file).await
|
||||
General::from_file(file).await
|
||||
};
|
||||
for (zone, builder) in mainconfig.dns.iter() {
|
||||
dns_manager.add_builder(zone.clone(), builder.clone()).await;
|
||||
|
@ -149,7 +136,7 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
|||
continue;
|
||||
},
|
||||
};
|
||||
let mut site = SiteConfig::from_file(file).await;
|
||||
let mut site = Site::from_file(file).await;
|
||||
site.name = filename.file_stem().unwrap().to_string_lossy().to_string();
|
||||
siteconfigs.push(site);
|
||||
}
|
||||
|
@ -204,7 +191,7 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
|||
|
||||
fn serialize_with_formatter<T: Serialize, F: Formatter>(value: &T, formatter: F) -> Result<String, Error> {
|
||||
let mut store = Vec::with_capacity(2 ^ 10);
|
||||
let mut serializer = serde_json::ser::Serializer::with_formatter(&mut store, formatter);
|
||||
let mut serializer = Serializer::with_formatter(&mut store, formatter);
|
||||
match value.serialize(&mut serializer) {
|
||||
Ok(_) => {},
|
||||
Err(error) => return Error::err(format!("Failed to Serialize the schema: {error}")),
|
||||
|
@ -218,7 +205,7 @@ async fn schema_generator() -> Result<(), Error> {
|
|||
let mut schema_settings = SchemaSettings::default();
|
||||
schema_settings.meta_schema = Some(DRAFT07.into());
|
||||
let mut generator = SchemaGenerator::new(schema_settings);
|
||||
let general_schema = serialize_with_formatter(&generator.root_schema_for::<GeneralConfig>(), formatter.clone())?;
|
||||
let general_schema = serialize_with_formatter(&generator.root_schema_for::<General>(), formatter.clone())?;
|
||||
match FILE_MODE_WRITE.clone().create_new(false).open("schema-general.json").await {
|
||||
Ok(mut file) => {
|
||||
match file.write(general_schema.as_bytes()).await {
|
||||
|
@ -229,7 +216,7 @@ async fn schema_generator() -> Result<(), Error> {
|
|||
Err(error) => return Err(Error::from_display(error)),
|
||||
};
|
||||
|
||||
let site_schema = serialize_with_formatter(&generator.root_schema_for::<SiteConfig>(), formatter.clone())?;
|
||||
let site_schema = serialize_with_formatter(&generator.root_schema_for::<Site>(), formatter.clone())?;
|
||||
match FILE_MODE_WRITE.clone().create_new(false).open("schema-site.json").await {
|
||||
Ok(mut file) => {
|
||||
match file.write(site_schema.as_bytes()).await {
|
||||
|
@ -243,7 +230,7 @@ async fn schema_generator() -> Result<(), Error> {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
log_init();
|
||||
logging();
|
||||
let args = Arguments::parse();
|
||||
if args.subcommands.is_none() && !check_permissions() {
|
||||
error!(
|
||||
|
@ -251,7 +238,7 @@ fn main() {
|
|||
);
|
||||
exit(4)
|
||||
}
|
||||
let runtime = match tokio::runtime::Builder::new_current_thread().enable_all().build() {
|
||||
let runtime = match Builder::new_current_thread().enable_all().build() {
|
||||
Ok(runtime) => runtime,
|
||||
Err(error) => {
|
||||
error!("Could not initialize Tokio runtime: {error}");
|
||||
|
|
|
@ -5,7 +5,10 @@ use std::{
|
|||
},
|
||||
fs::Permissions,
|
||||
os::{
|
||||
fd::AsFd,
|
||||
fd::{
|
||||
AsFd as _,
|
||||
AsRawFd as _,
|
||||
},
|
||||
unix::fs::{
|
||||
PermissionsExt as _,
|
||||
fchown,
|
||||
|
@ -25,14 +28,17 @@ use crate::{
|
|||
MODE_SECRETS,
|
||||
WAIT_TIME,
|
||||
},
|
||||
load_privkey,
|
||||
prelude::*,
|
||||
types::{
|
||||
self,
|
||||
config::CA,
|
||||
cryptography::Algorithm,
|
||||
cryptography::{
|
||||
Algorithm,
|
||||
Strength,
|
||||
},
|
||||
dns::Manager,
|
||||
structs::{
|
||||
Certificate,
|
||||
Error,
|
||||
ProcessorArgs,
|
||||
San,
|
||||
|
@ -57,9 +63,14 @@ use acme2_eab::{
|
|||
OrderBuilder,
|
||||
OrderStatus,
|
||||
};
|
||||
use libc::fchmod;
|
||||
use log::*;
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
pkey::{
|
||||
PKey,
|
||||
Private,
|
||||
},
|
||||
stack::Stack,
|
||||
x509::{
|
||||
X509,
|
||||
|
@ -74,6 +85,7 @@ use openssl::{
|
|||
},
|
||||
},
|
||||
};
|
||||
use pem::parse as pem_parse;
|
||||
use reqwest::Client;
|
||||
use tokio::{
|
||||
fs::{
|
||||
|
@ -86,7 +98,10 @@ use tokio::{
|
|||
},
|
||||
join,
|
||||
};
|
||||
use zbus_systemd::systemd1;
|
||||
use zbus_systemd::{
|
||||
systemd1,
|
||||
zbus::Connection,
|
||||
};
|
||||
|
||||
fn gen_stack(args: &ProcessorArgs, context: X509v3Context) -> Stack<X509Extension> {
|
||||
let mut stack = Stack::new().unwrap();
|
||||
|
@ -109,7 +124,7 @@ fn gen_stack(args: &ProcessorArgs, context: X509v3Context) -> Stack<X509Extensio
|
|||
}
|
||||
|
||||
pub async fn accounts(
|
||||
name: &String,
|
||||
name: &str,
|
||||
ca: &CA,
|
||||
directories: &mut HashMap<String, Arc<Directory>>,
|
||||
accounts: &mut HashMap<String, Arc<Account>>,
|
||||
|
@ -125,7 +140,12 @@ pub async fn accounts(
|
|||
dir
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Failed to initialize directory for ca {name}: {error}");
|
||||
match error {
|
||||
acme2_eab::Error::Server(server_error) => error!("Failed to get the directory(Server Error): {server_error}"),
|
||||
acme2_eab::Error::Transport(error) => error!("Failed to connect to the CA(Transport error): {error}"),
|
||||
acme2_eab::Error::Other(error) => error!("unexpected error(other): {error}"),
|
||||
x => error!("Unexpected Error: {x}"),
|
||||
}
|
||||
return;
|
||||
},
|
||||
}
|
||||
|
@ -141,14 +161,14 @@ pub async fn accounts(
|
|||
debug!("No Email address given")
|
||||
},
|
||||
}
|
||||
let accountkey = accountpath.join("file.pem").with_file_name(name.clone());
|
||||
let accountkey = accountpath.join("file.pem").with_file_name(name);
|
||||
let mut accountkeyfile = None;
|
||||
if accountkey.exists() {
|
||||
if let Ok(key) = load_privkey(accountkey).await {
|
||||
ac.private_key(key);
|
||||
}
|
||||
} else {
|
||||
info!("creating new key for the account {}", name.clone());
|
||||
info!("creating new key for the account {}", name.to_owned());
|
||||
accountkeyfile = match FILE_MODE_OVERWRITE.clone().mode(MODE_SECRETS).open(accountkey).await {
|
||||
Ok(file) => Some(file),
|
||||
Err(error) => {
|
||||
|
@ -193,7 +213,7 @@ pub async fn accounts(
|
|||
}
|
||||
let account = match ac.build().await {
|
||||
Ok(account) => {
|
||||
accounts.insert(name.clone(), Arc::clone(&account));
|
||||
accounts.insert(name.to_owned(), Arc::clone(&account));
|
||||
account
|
||||
},
|
||||
Err(error) => {
|
||||
|
@ -209,6 +229,120 @@ pub async fn accounts(
|
|||
}
|
||||
}
|
||||
|
||||
async fn load_privkey(path: PathBuf) -> Result<PKey<Private>, Error> {
|
||||
let mut file = match FILE_MODE.open(path).await {
|
||||
Ok(file) => file,
|
||||
Err(error) => return Error::err(format!("Failed to open Private Key: {error}")),
|
||||
};
|
||||
let mut data = String::with_capacity(file.metadata().await.map(|metadata| metadata.len()).unwrap_or_default() as usize);
|
||||
if let Err(error) = file.read_to_string(&mut data).await {
|
||||
return Error::err(format!("Failed to read data for the key: {error}"));
|
||||
}
|
||||
match PKey::private_key_from_pem(data.as_bytes()) {
|
||||
Ok(key) => Ok(key),
|
||||
Err(error) => Error::err(format!("Failed to parse pem data: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_or_create_privkey(
|
||||
path: PathBuf,
|
||||
owner: Option<u32>,
|
||||
group: Option<u32>,
|
||||
mode: Option<u32>,
|
||||
algorithm: Algorithm,
|
||||
strength: Strength,
|
||||
) -> types::Result<(PKey<Private>, bool)> {
|
||||
let mut key_changed = false;
|
||||
let privkey;
|
||||
if path.exists() {
|
||||
match load_privkey(path.clone()).await {
|
||||
Ok(key) => {
|
||||
if !key.matches(algorithm, strength) {
|
||||
info!("Regenerating Key: Parameters changed");
|
||||
privkey = gen_key(algorithm, strength)?;
|
||||
key_changed = true;
|
||||
} else {
|
||||
privkey = key;
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Failed to load key: {error}");
|
||||
key_changed = true;
|
||||
privkey = gen_key(algorithm, strength)?;
|
||||
},
|
||||
}
|
||||
} else {
|
||||
key_changed = true;
|
||||
privkey = gen_key(algorithm, strength)?;
|
||||
}
|
||||
if key_changed {
|
||||
let mut keyfile = match FILE_MODE_OVERWRITE.clone().open(path.clone()).await {
|
||||
Ok(file) => file,
|
||||
Err(error) => return Error::err(format!("Failed to write key: {error}")),
|
||||
};
|
||||
if let Err(error) = fchown(keyfile.as_fd(), owner, group) {
|
||||
error!("Failed to change owner of the private key: {error}")
|
||||
}
|
||||
if let Some(mode) = mode {
|
||||
match unsafe { fchmod(keyfile.as_raw_fd(), mode) } {
|
||||
0 => {},
|
||||
libc::EROFS => error!("Not enough Permissions to change the mode of the private key"),
|
||||
libc::EINVAL => error!("Invalid Mode for the private key"),
|
||||
libc::EINTR => warn!("chmod was interrupted by an signal"),
|
||||
err => error!("unkown return code from fchmod: {err}"),
|
||||
}
|
||||
}
|
||||
let pkey = match privkey.private_key_to_pem_pkcs8() {
|
||||
Ok(pkey) => pkey,
|
||||
Err(error) => return Err(Error::from_display(error)),
|
||||
};
|
||||
if let Err(error) = keyfile.write_all(pkey.as_slice()).await {
|
||||
return Err(Error::from_display(error));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((privkey, key_changed))
|
||||
}
|
||||
|
||||
async fn load_public_key(path: PathBuf) -> types::Result<Certificate> {
|
||||
let mut file = match FILE_MODE.open(path).await {
|
||||
Ok(file) => file,
|
||||
Err(error) => return Err(Error::from_display(error)),
|
||||
};
|
||||
let mut data = String::with_capacity(file.metadata().await.map(|metadata| metadata.len()).unwrap_or_default() as usize);
|
||||
if let Err(error) = file.read_to_string(&mut data).await {
|
||||
return Error::err(format!("Failed to read the public key: {error}"));
|
||||
}
|
||||
let pem = match pem_parse(data.as_bytes()) {
|
||||
Ok(pem) => pem,
|
||||
Err(error) => return Error::err(format!("Failed to parse PEM file: {error}")),
|
||||
};
|
||||
|
||||
let cert = match X509::from_der(pem.contents()) {
|
||||
Ok(cert) => cert,
|
||||
Err(error) => {
|
||||
return Error::err(format!(
|
||||
"Failed to parse the certificate:\n- {}",
|
||||
error
|
||||
.errors()
|
||||
.iter()
|
||||
.map(|err| err.to_string())
|
||||
.reduce(|mut errorlist, item| {
|
||||
errorlist.push_str("\n- ");
|
||||
errorlist.push_str(item.as_str());
|
||||
errorlist
|
||||
})
|
||||
.unwrap_or_default()
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Certificate {
|
||||
account_id: pem.headers().get("account_id").map(|d| d.to_string()),
|
||||
cert,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub async fn site(args: ProcessorArgs<'_>) {
|
||||
let mut cert_renew = false;
|
||||
|
@ -222,62 +356,43 @@ pub async fn site(args: ProcessorArgs<'_>) {
|
|||
cert_renew = true;
|
||||
}
|
||||
let (uid, gid) = get_uid_gid(args.owner(), args.group());
|
||||
let mut private_key;
|
||||
// Private key block
|
||||
{
|
||||
let private_key_file = directory.join("privkey.pem");
|
||||
let mut write_pkey = false;
|
||||
if !private_key_file.exists() {
|
||||
cert_renew = true;
|
||||
write_pkey = true;
|
||||
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 !private_key.matches(args.algorithm(), args.strength()) {
|
||||
info!("Algorithm for the private key has changed, updating the key");
|
||||
cert_renew = true;
|
||||
write_pkey = true;
|
||||
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 {
|
||||
error!("Failed to parse the private key. Renewing the private key.");
|
||||
write_pkey = true;
|
||||
cert_renew = true;
|
||||
private_key = match_error!(gen_key(args.algorithm(), args.strength())=>Err(error)->"Aborting processing the site due to problem with the certificate generation: {error}");
|
||||
}
|
||||
if write_pkey {
|
||||
let pkey = private_key.private_key_to_pem_pkcs8().unwrap();
|
||||
let mut file = match_error!(FILE_MODE_OVERWRITE.clone().mode(MODE_PRIVATE).open(private_key_file.clone()).await=>Err(error)->"Failed to write new private key: {error}");
|
||||
#[cfg(feature = "capabilities")]
|
||||
if let Err(error) = fchown(file.as_fd(), uid, gid) {
|
||||
error!("Failed to change owner of the new privatekey: {error}");
|
||||
return;
|
||||
}
|
||||
match_error!(file.write_all(&pkey).await=>Err(error)->"Failed to write new private key: {error}");
|
||||
}
|
||||
}
|
||||
let private_key = match load_or_create_privkey(directory.join("privkey.pem"), uid, gid, Some(MODE_SECRETS), args.algorithm(), args.strength()).await {
|
||||
Ok((key, changes)) => {
|
||||
cert_renew |= changes;
|
||||
key
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Failed to load or to create the key: {error}");
|
||||
return;
|
||||
},
|
||||
};
|
||||
let pubkey_filename = directory.join("pubkey.pem");
|
||||
if pubkey_filename.exists() {
|
||||
let mut file = match_error!(FILE_MODE.open(pubkey_filename.clone()).await=>Err(error)->"Failed to open publickey. Aborting processing: {error}");
|
||||
let mut data = String::new();
|
||||
if let Err(error) = file.read_to_string(&mut data).await {
|
||||
cert_renew = true;
|
||||
error!("Failed to read public key: {error}")
|
||||
} else {
|
||||
let pubkey = match X509::from_pem(data.as_bytes()) {
|
||||
Ok(key) => key,
|
||||
Err(_) => todo!(),
|
||||
};
|
||||
if !pubkey.days_left(args.refresh_time()) {
|
||||
let exists = pubkey_filename.exists();
|
||||
|
||||
let publickey = load_public_key(pubkey_filename.clone()).await;
|
||||
if exists {
|
||||
if let Ok(pubkey) = publickey.clone() {
|
||||
if let Some(account) = pubkey.account_id {
|
||||
if account != args.account().id {
|
||||
info!("Account changed");
|
||||
cert_renew = true;
|
||||
}
|
||||
}
|
||||
if !pubkey.cert.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");
|
||||
}
|
||||
if !pubkey.cert.match_san(args.san()) {
|
||||
info!("Subject Alternative Names differ from Certificate");
|
||||
cert_renew = true;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if !exists || publickey.is_err() {
|
||||
cert_renew = true;
|
||||
if let Err(error) = publickey {
|
||||
error!("Failed to parse the public key: {error}")
|
||||
}
|
||||
}
|
||||
if !cert_renew {
|
||||
info!("Site {} doesn't need an update for the certificate.", args.name());
|
||||
|
@ -450,7 +565,7 @@ pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, manager:
|
|||
}
|
||||
|
||||
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 conn = match_error!(Connection::system().await=>Err(error)-> "Failed to connect with the systemd manager: {error}");
|
||||
|
||||
let systemd_manager = systemd1::ManagerProxy::new(&conn).await.unwrap();
|
||||
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
use crate::{
|
||||
prelude::*,
|
||||
types::{
|
||||
self,
|
||||
VString,
|
||||
cryptography::{
|
||||
Algorithm,
|
||||
Strength,
|
||||
},
|
||||
dns::DnsBuilder,
|
||||
dns::Builder,
|
||||
structs::Error,
|
||||
},
|
||||
utils::schema::{
|
||||
email_transform,
|
||||
uri_transform,
|
||||
},
|
||||
};
|
||||
|
||||
use macro_rules_attribute::macro_rules_derive;
|
||||
|
@ -26,27 +31,27 @@ use std::{
|
|||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GeneralConfig {
|
||||
#[serde(default = "GeneralConfig::default_accounts")]
|
||||
pub struct General {
|
||||
#[serde(default = "General::default_accounts")]
|
||||
pub accounts_path: String,
|
||||
#[serde(default = "GeneralConfig::default_sites")]
|
||||
#[serde(default = "General::default_sites")]
|
||||
pub sites_path: String,
|
||||
#[serde(default = "GeneralConfig::default_challenge")]
|
||||
#[serde(default = "General::default_challenge")]
|
||||
pub http_challenge_path: Option<String>,
|
||||
|
||||
/// This contains the domains(Keys) and the DNS-Servers(values) that are responsible for it.
|
||||
#[serde(default = "GeneralConfig::default_dns")]
|
||||
pub dns: HashMap<String, DnsBuilder>,
|
||||
#[serde(default = "GeneralConfig::default_certificates")]
|
||||
#[serde(default = "General::default_dns")]
|
||||
pub dns: HashMap<String, Builder>,
|
||||
#[serde(default = "General::default_certificates")]
|
||||
pub certificates_path: String,
|
||||
|
||||
/// The Key of this table describe an nickname for an CA.
|
||||
/// Letsencrypt Prod and Staging are builtin configured, so they doesn't have to be configured.
|
||||
#[serde(default = "GeneralConfig::default_cas")]
|
||||
#[serde(default = "General::default_cas")]
|
||||
pub ca: HashMap<String, CA>,
|
||||
}
|
||||
|
||||
impl GeneralConfig {
|
||||
impl General {
|
||||
#[inline]
|
||||
pub(super) fn default_accounts() -> String {
|
||||
"accounts".into()
|
||||
|
@ -63,7 +68,7 @@ impl GeneralConfig {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn default_dns() -> HashMap<String, DnsBuilder> {
|
||||
pub(super) fn default_dns() -> HashMap<String, Builder> {
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
|
@ -90,7 +95,7 @@ pub struct Eab {
|
|||
}
|
||||
|
||||
impl Eab {
|
||||
pub fn key(&self) -> super::Result<PKey<Private>> {
|
||||
pub fn key(&self) -> types::Result<PKey<Private>> {
|
||||
let decoded = &match_error!(data_encoding::BASE64URL_NOPAD.decode(self.key.as_bytes())=>Err(error)-> "Failed to decode the HMAC key for the eab_key: {error}", Error::err("Failed to decode eab_key"));
|
||||
PKey::hmac(decoded).map_err(|error| Error::new(format!("Failed to parse the private key: {error}")))
|
||||
}
|
||||
|
@ -101,11 +106,11 @@ impl Eab {
|
|||
#[serde(deny_unknown_fields)]
|
||||
pub struct CA {
|
||||
/// Url for the directory
|
||||
#[schemars(transform=crate::utils::schema::uri_transform)]
|
||||
#[schemars(transform=uri_transform)]
|
||||
pub directory: String,
|
||||
|
||||
/// Email addresses for the CA to contact the user
|
||||
#[schemars(transform=crate::utils::schema::email_transform)]
|
||||
#[schemars(transform=email_transform)]
|
||||
pub email_addresses: Option<VString>,
|
||||
|
||||
#[serde(flatten, default)]
|
||||
|
@ -130,7 +135,7 @@ impl CA {
|
|||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SiteConfig {
|
||||
pub struct Site {
|
||||
/// The Configured Certificate Authority
|
||||
pub ca: String,
|
||||
|
||||
|
@ -143,7 +148,7 @@ pub struct SiteConfig {
|
|||
|
||||
/// EmailAdresses that this Certificate is valid for
|
||||
#[serde(default)]
|
||||
#[schemars(transform=crate::utils::schema::email_transform)]
|
||||
#[schemars(transform=email_transform)]
|
||||
pub emails: VString,
|
||||
|
||||
/// The systemd services are reloaded
|
||||
|
|
|
@ -5,6 +5,7 @@ use serde::Deserialize;
|
|||
use crate::{
|
||||
macros::DefDer,
|
||||
types::{
|
||||
self,
|
||||
dns::Dns,
|
||||
structs::DnsToken,
|
||||
traits::DnsHandler,
|
||||
|
@ -26,7 +27,7 @@ impl DNSUpdateClientOptions {
|
|||
pub struct DnsUpdateHandler {}
|
||||
|
||||
impl DnsHandler for DnsUpdateHandler {
|
||||
async fn set_record(&self, _domain: String, _content: String) -> crate::types::Result<DnsToken> {
|
||||
async fn set_record(&self, _domain: String, _content: String) -> types::Result<DnsToken> {
|
||||
Ok(DnsToken::new_dns_update())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ pub(super) mod pdns;
|
|||
use crate::{
|
||||
macros::DefDer,
|
||||
types::{
|
||||
self,
|
||||
dns::{
|
||||
dnsupdate::{
|
||||
DNSUpdateClientOptions,
|
||||
|
@ -75,7 +76,7 @@ impl Manager {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn add_builder(&mut self, zone: String, builder: DnsBuilder) {
|
||||
pub async fn add_builder(&mut self, zone: String, builder: Builder) {
|
||||
let mut fixed_zone = zone.clone();
|
||||
if !fixed_zone.ends_with('.') {
|
||||
fixed_zone.push('.');
|
||||
|
@ -83,9 +84,9 @@ impl Manager {
|
|||
self.0.lock().await.servers.insert(
|
||||
fixed_zone,
|
||||
match builder {
|
||||
DnsBuilder::PowerDNS(pdns_client_options) => pdns_client_options.build(zone, self.1.clone()),
|
||||
DnsBuilder::DNSUpdate(dnsupdate_client_options) => dnsupdate_client_options.build(zone),
|
||||
DnsBuilder::None => Dns::None,
|
||||
Builder::PowerDNS(pdns_client_options) => pdns_client_options.build(zone, self.1.clone()),
|
||||
Builder::DNSUpdate(dnsupdate_client_options) => dnsupdate_client_options.build(zone),
|
||||
Builder::None => Dns::None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -103,7 +104,7 @@ struct InnerManager {
|
|||
#[derive(Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
pub enum DnsBuilder {
|
||||
pub enum Builder {
|
||||
PowerDNS(PdnsClientOptions),
|
||||
DNSUpdate(DNSUpdateClientOptions),
|
||||
#[default]
|
||||
|
@ -118,7 +119,7 @@ pub enum Dns {
|
|||
}
|
||||
|
||||
impl Dns {
|
||||
pub async fn set_record(&self, domain: String, content: String) -> crate::types::Result<DnsToken> {
|
||||
pub async fn set_record(&self, domain: String, content: String) -> types::Result<DnsToken> {
|
||||
match self {
|
||||
Dns::PowerDNS(pdns_handler) => pdns_handler.set_record(domain, content).await,
|
||||
Dns::DNSUpdate(dns_update_handler) => dns_update_handler.set_record(domain, content).await,
|
||||
|
|
|
@ -21,6 +21,7 @@ use serde::{
|
|||
use crate::{
|
||||
macros::DefDer,
|
||||
types::{
|
||||
self,
|
||||
dns::Dns,
|
||||
structs::{
|
||||
DnsToken,
|
||||
|
@ -83,7 +84,7 @@ fn fix_url(baseurl: Url, path: String) -> Result<Url, ()> {
|
|||
|
||||
|
||||
impl DnsHandler for PdnsHandler {
|
||||
async fn set_record(&self, mut domain: String, content: String) -> crate::types::Result<DnsToken> {
|
||||
async fn set_record(&self, mut domain: String, content: String) -> types::Result<DnsToken> {
|
||||
trace!("Original URL: {}", self.server);
|
||||
let baseurl = match reqwest::Url::parse(&self.server) {
|
||||
Ok(url) => {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use std::{
|
||||
fmt::Display,
|
||||
fmt::{
|
||||
Display,
|
||||
Formatter,
|
||||
Result as fmtResult,
|
||||
},
|
||||
net::IpAddr,
|
||||
};
|
||||
|
||||
|
@ -7,7 +11,7 @@ use acme2_eab::Identifier;
|
|||
use openssl::x509::GeneralName;
|
||||
|
||||
use crate::types::{
|
||||
config::GeneralConfig,
|
||||
config::General,
|
||||
dns::pdns::PdnsError,
|
||||
structs::{
|
||||
Error,
|
||||
|
@ -16,7 +20,7 @@ use crate::types::{
|
|||
};
|
||||
|
||||
|
||||
impl Default for GeneralConfig {
|
||||
impl Default for General {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -78,14 +82,14 @@ impl From<San> for Identifier {
|
|||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmtResult {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Display for PdnsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmtResult {
|
||||
f.write_str(&self.error)?;
|
||||
if !self.errors.is_empty() {
|
||||
f.write_str("(")?;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::collections::HashSet;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
result::Result as stdResult,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
|
||||
pub mod config;
|
||||
pub mod cryptography;
|
||||
pub mod dns;
|
||||
|
@ -9,10 +11,11 @@ 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>>;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, structs::Error>;
|
||||
pub type Result<T> = stdResult<T, structs::Error>;
|
||||
|
|
|
@ -16,6 +16,7 @@ use clap::{
|
|||
};
|
||||
use derive_new::new;
|
||||
use macro_rules_attribute::macro_rules_derive;
|
||||
use openssl::x509::X509;
|
||||
use reqwest::{
|
||||
Client,
|
||||
RequestBuilder,
|
||||
|
@ -26,7 +27,7 @@ use crate::{
|
|||
prelude::*,
|
||||
types::{
|
||||
SafeSet,
|
||||
config::SiteConfig,
|
||||
config::Site,
|
||||
cryptography::{
|
||||
Algorithm,
|
||||
Strength,
|
||||
|
@ -58,7 +59,7 @@ pub enum SubCommand {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
#[derive(new)]
|
||||
pub struct ProcessorArgs<'a> {
|
||||
site: SiteConfig,
|
||||
site: Site,
|
||||
account: Arc<Account>,
|
||||
reload_services: &'a SafeSet<String>,
|
||||
restart_services: &'a SafeSet<String>,
|
||||
|
@ -190,3 +191,9 @@ impl DnsToken {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
pub struct Certificate {
|
||||
pub cert: X509,
|
||||
pub account_id: Option<String>,
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ use openssl::{
|
|||
x509::X509,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt::Debug as fmtDebug;
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::AsyncReadExt,
|
||||
|
@ -122,6 +123,6 @@ impl MatchX509 for X509 {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait DnsHandler: std::fmt::Debug + Send {
|
||||
pub trait DnsHandler: fmtDebug + Send {
|
||||
async fn set_record(&self, domain: String, content: String) -> types::Result<DnsToken>;
|
||||
}
|
||||
|
|
47
src/utils.rs
47
src/utils.rs
|
@ -1,4 +1,10 @@
|
|||
use std::ffi::CString;
|
||||
use std::{
|
||||
env::{
|
||||
self,
|
||||
VarError,
|
||||
},
|
||||
ffi::CString,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
consts::{
|
||||
|
@ -38,6 +44,7 @@ use openssl::{
|
|||
X509NameBuilder,
|
||||
},
|
||||
};
|
||||
use systemd_journal_logger::JournalLog;
|
||||
|
||||
#[cfg(feature = "capabilities")]
|
||||
const CAPABILITY_SET: CapSet = CapSet::Permitted;
|
||||
|
@ -249,3 +256,41 @@ pub mod schema {
|
|||
type_transform(schema, "uri");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn logging() {
|
||||
let level = match env::var("RUST_LOG") {
|
||||
Ok(levelname) => levelname.to_uppercase(),
|
||||
Err(error) => {
|
||||
match error {
|
||||
VarError::NotPresent => {},
|
||||
VarError::NotUnicode(text) => println!("Invalid Log Level {}", text.display()),
|
||||
};
|
||||
"INFO".to_string()
|
||||
},
|
||||
};
|
||||
let journal = JournalLog::new().map(|logger| logger.with_extra_fields(vec![("PKG_VERSION", env!("CARGO_PKG_VERSION"))]));
|
||||
match journal {
|
||||
Ok(logger) => {
|
||||
match logger.install() {
|
||||
Ok(()) => {
|
||||
log::set_max_level(match level.as_str() {
|
||||
"OFF" => LevelFilter::Off,
|
||||
"TRACE" => LevelFilter::Trace,
|
||||
"DEBUG" => LevelFilter::Debug,
|
||||
"WARN" => LevelFilter::Warn,
|
||||
"ERROR" => LevelFilter::Error,
|
||||
_ => LevelFilter::Info,
|
||||
});
|
||||
},
|
||||
Err(error) => {
|
||||
env_logger::init();
|
||||
warn!("Failed to initialize the Journal Logger: {error}")
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
env_logger::init();
|
||||
warn!("Failed to initialize the Journal Logger: {error}")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren