Current version with an rudimentary dns update
Dieser Commit ist enthalten in:
Ursprung
b4d11f0abe
Commit
4cb07326a7
18 geänderte Dateien mit 1375 neuen und 498 gelöschten Zeilen
|
@ -6,4 +6,3 @@ SSL_CERT_FILE="/etc/ca-certificates/extracted/tls-ca-bundle.pem"
|
||||||
|
|
||||||
[target]
|
[target]
|
||||||
[target.'cfg(debug_assertions)']
|
[target.'cfg(debug_assertions)']
|
||||||
runner = "strace -e trace=open,openat -P /etc/*"
|
|
||||||
|
|
12
.editorconfig
Normale Datei
12
.editorconfig
Normale Datei
|
@ -0,0 +1,12 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = false
|
765
Cargo.lock
generiert
765
Cargo.lock
generiert
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
|
@ -8,8 +8,11 @@ resolver = "3"
|
||||||
unstable = []
|
unstable = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
derive-new = "0.7.0"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
lazy_static = "1.5"
|
lazy_static = "1.5"
|
||||||
|
schemars = { version = "0.9.0", default-features = false, features = ["derive", "std", "preserve_order"] }
|
||||||
|
serde_json = { version = "1.0.140", default-features = false, features = ["std"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
[dependencies.caps]
|
[dependencies.caps]
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
|
|
137
schema-general.json
Normale Datei
137
schema-general.json
Normale Datei
|
@ -0,0 +1,137 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "GeneralConfig",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"accounts_path": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "accounts"
|
||||||
|
},
|
||||||
|
"sites_path": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "sites"
|
||||||
|
},
|
||||||
|
"http_challenge_path": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/$defs/DnsBuilder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"certificates_path": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "certificates"
|
||||||
|
},
|
||||||
|
"ca": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"$ref": "#/$defs/CA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"$defs": {
|
||||||
|
"DnsBuilder": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "powerdns"
|
||||||
|
},
|
||||||
|
"api_key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"server_id": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"api_key",
|
||||||
|
"server"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "dnsupdate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CA": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"directory": {
|
||||||
|
"description": "Url for the directory",
|
||||||
|
"type": "uri"
|
||||||
|
},
|
||||||
|
"email_addresses": {
|
||||||
|
"description": "Email addresses for the CA to contact the user",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "email"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eab_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"eab_key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"renew_before": {
|
||||||
|
"description": "Amount of days the certificate is renewed before the Certificate is outdated\n TODO: give to processor",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 90,
|
||||||
|
"default": 7
|
||||||
|
},
|
||||||
|
"tos_accepted": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"directory"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
197
schema-site.json
Normale Datei
197
schema-site.json
Normale Datei
|
@ -0,0 +1,197 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "SiteConfig",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ca": {
|
||||||
|
"description": "The Configured Certificate Authority",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"domains": {
|
||||||
|
"description": "The Domains this site is responsible for",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"addresses": {
|
||||||
|
"description": "IPAddresses for the Certificate",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "ip"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"emails": {
|
||||||
|
"description": "EmailAdresses that this Certificate is valid for",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "email"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"reload_services": {
|
||||||
|
"description": "The systemd services are reloaded",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"restart_services": {
|
||||||
|
"description": "The Systemd-Services have to be restarted to get the new certificates",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"trigger_commands": {
|
||||||
|
"description": "Commands that have to be run after the certificates have been issued if they don't have an systemd service",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"algorithm": {
|
||||||
|
"description": "The Algorithm for the Private Key",
|
||||||
|
"$ref": "#/$defs/Algorithm"
|
||||||
|
},
|
||||||
|
"strength": {
|
||||||
|
"description": "The Strength of the Private key.",
|
||||||
|
"$ref": "#/$defs/Strength"
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"description": "Owner of the Certificate and private key",
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"description": "Group of the Certificate and private key",
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"ca",
|
||||||
|
"domains"
|
||||||
|
],
|
||||||
|
"$defs": {
|
||||||
|
"DnsBuilder": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "powerdns"
|
||||||
|
},
|
||||||
|
"api_key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"server_id": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"api_key",
|
||||||
|
"server"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "dnsupdate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CA": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"directory": {
|
||||||
|
"description": "Url for the directory",
|
||||||
|
"type": "uri"
|
||||||
|
},
|
||||||
|
"email_addresses": {
|
||||||
|
"description": "Email addresses for the CA to contact the user",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "email"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eab_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"eab_key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"renew_before": {
|
||||||
|
"description": "Amount of days the certificate is renewed before the Certificate is outdated\n TODO: give to processor",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 90,
|
||||||
|
"default": 7
|
||||||
|
},
|
||||||
|
"tos_accepted": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"directory"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Algorithm": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Rsa",
|
||||||
|
"Brainpool",
|
||||||
|
"Secp",
|
||||||
|
"ED25519"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Strength": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Weak",
|
||||||
|
"Middle",
|
||||||
|
"Strong"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
src/main.rs
78
src/main.rs
|
@ -1,7 +1,9 @@
|
||||||
//! Acme client that supports multiple CAs and configs for sites that can be seperate from the mainconfig
|
//! Acme client that supports multiple CAs and configs for sites that can be seperate from the mainconfig
|
||||||
#![allow(dead_code)]
|
|
||||||
#![allow(clippy::clone_on_copy)]
|
#![allow(clippy::clone_on_copy)]
|
||||||
#![allow(clippy::identity_op)]
|
#![allow(clippy::identity_op)]
|
||||||
|
#![allow(refining_impl_trait)]
|
||||||
|
#![allow(clippy::collapsible_if)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub(crate) mod consts;
|
pub(crate) mod consts;
|
||||||
pub(crate) mod macros;
|
pub(crate) mod macros;
|
||||||
|
@ -18,11 +20,15 @@ use crate::{
|
||||||
GeneralConfig,
|
GeneralConfig,
|
||||||
SiteConfig,
|
SiteConfig,
|
||||||
},
|
},
|
||||||
|
dns::Manager,
|
||||||
structs::{
|
structs::{
|
||||||
Arguments,
|
Arguments,
|
||||||
|
Error,
|
||||||
ProcessorArgs,
|
ProcessorArgs,
|
||||||
|
SubCommand,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
utils::check_permissions,
|
||||||
};
|
};
|
||||||
use acme2_eab::Directory;
|
use acme2_eab::Directory;
|
||||||
use async_scoped::TokioScope;
|
use async_scoped::TokioScope;
|
||||||
|
@ -38,6 +44,16 @@ use openssl::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use reqwest::tls::Version;
|
use reqwest::tls::Version;
|
||||||
|
use schemars::{
|
||||||
|
SchemaGenerator,
|
||||||
|
consts::meta_schemas::DRAFT07,
|
||||||
|
generate::SchemaSettings,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::ser::{
|
||||||
|
Formatter,
|
||||||
|
PrettyFormatter,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{
|
collections::{
|
||||||
HashMap,
|
HashMap,
|
||||||
|
@ -57,15 +73,16 @@ use tokio::{
|
||||||
create_dir_all,
|
create_dir_all,
|
||||||
read_dir,
|
read_dir,
|
||||||
},
|
},
|
||||||
io::AsyncReadExt,
|
io::{
|
||||||
|
AsyncReadExt,
|
||||||
|
AsyncWriteExt,
|
||||||
|
},
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
use tokio_stream::{
|
use tokio_stream::{
|
||||||
StreamExt,
|
StreamExt,
|
||||||
wrappers::ReadDirStream,
|
wrappers::ReadDirStream,
|
||||||
};
|
};
|
||||||
use types::structs::Error;
|
|
||||||
use utils::check_permissions;
|
|
||||||
|
|
||||||
|
|
||||||
fn default_client() -> Result<reqwest::Client, Error> {
|
fn default_client() -> Result<reqwest::Client, Error> {
|
||||||
|
@ -94,6 +111,7 @@ async fn load_privkey(path: PathBuf) -> Result<PKey<Private>, Error> {
|
||||||
|
|
||||||
async fn racme(flags: Arguments) -> Result<(), Error> {
|
async fn racme(flags: Arguments) -> Result<(), Error> {
|
||||||
let client = default_client()?;
|
let client = default_client()?;
|
||||||
|
let dns_manager = Manager::new();
|
||||||
let systemd_access = daemon::booted();
|
let systemd_access = daemon::booted();
|
||||||
let mainconfig = {
|
let mainconfig = {
|
||||||
let file = match FILE_MODE.open(flags.config).await {
|
let file = match FILE_MODE.open(flags.config).await {
|
||||||
|
@ -151,7 +169,6 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let challengepath = mainconfig.http_challenge_path.and_then(|path| PathBuf::from_str(path.as_str()).ok());
|
let challengepath = mainconfig.http_challenge_path.and_then(|path| PathBuf::from_str(path.as_str()).ok());
|
||||||
let dnsserver = None;
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
TokioScope::scope_and_collect(|scope| {
|
TokioScope::scope_and_collect(|scope| {
|
||||||
|
@ -164,7 +181,8 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
||||||
&restart_services,
|
&restart_services,
|
||||||
certs.clone(),
|
certs.clone(),
|
||||||
challengepath.clone(),
|
challengepath.clone(),
|
||||||
dnsserver.clone(),
|
dns_manager.clone(),
|
||||||
|
client.clone(),
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
error!("Could not process site {} because of previous errors", site.name)
|
error!("Could not process site {} because of previous errors", site.name)
|
||||||
|
@ -180,9 +198,50 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
match value.serialize(&mut serializer) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(error) => return Error::err(format!("Failed to Serialize the schema: {error}")),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(unsafe { String::from_utf8_unchecked(store) })
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn schema_generator() -> Result<(), Error> {
|
||||||
|
let formatter = PrettyFormatter::with_indent(&[b' '; 4]);
|
||||||
|
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())?;
|
||||||
|
match FILE_MODE_WRITE.clone().create_new(false).open("schema-general.json").await {
|
||||||
|
Ok(mut file) => {
|
||||||
|
match file.write(general_schema.as_bytes()).await {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(error) => return Error::err(format!("{error}")),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => return Err(Error::from_display(error)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let site_schema = serialize_with_formatter(&generator.root_schema_for::<SiteConfig>(), 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 {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(error) => return Error::err(format!("{error}")),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => return Err(Error::from_display(error)),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
log_init();
|
log_init();
|
||||||
if !check_permissions() {
|
let args = Arguments::parse();
|
||||||
|
if args.subcommands.is_none() && !check_permissions() {
|
||||||
error!(
|
error!(
|
||||||
"This program needs the capability to change the ownership and the permissions of files. this can be done via adding the capability via `capsh --caps=\"cap_chown+ep cap_fowner+ep\" --shell=racme -- racme.toml`, systemd service setting AmbientCapabilities or running as root(not recommended)"
|
"This program needs the capability to change the ownership and the permissions of files. this can be done via adding the capability via `capsh --caps=\"cap_chown+ep cap_fowner+ep\" --shell=racme -- racme.toml`, systemd service setting AmbientCapabilities or running as root(not recommended)"
|
||||||
);
|
);
|
||||||
|
@ -195,7 +254,10 @@ fn main() {
|
||||||
exit(2)
|
exit(2)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let result = runtime.block_on(racme(Arguments::parse()));
|
let result = match args.subcommands {
|
||||||
|
None => runtime.block_on(racme(args)),
|
||||||
|
Some(SubCommand::Schema) => runtime.block_on(schema_generator()),
|
||||||
|
};
|
||||||
runtime.shutdown_timeout(Duration::from_secs(1));
|
runtime.shutdown_timeout(Duration::from_secs(1));
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
error!("{error}");
|
error!("{error}");
|
||||||
|
|
|
@ -25,12 +25,12 @@ use crate::{
|
||||||
load_privkey,
|
load_privkey,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
types::{
|
types::{
|
||||||
config::{
|
self,
|
||||||
CA,
|
config::CA,
|
||||||
Dns,
|
|
||||||
},
|
|
||||||
cryptography::Algorithm,
|
cryptography::Algorithm,
|
||||||
|
dns::Manager,
|
||||||
structs::{
|
structs::{
|
||||||
|
Error,
|
||||||
ProcessorArgs,
|
ProcessorArgs,
|
||||||
San,
|
San,
|
||||||
},
|
},
|
||||||
|
@ -45,6 +45,7 @@ use acme2_eab::{
|
||||||
Account,
|
Account,
|
||||||
AccountBuilder,
|
AccountBuilder,
|
||||||
Authorization,
|
Authorization,
|
||||||
|
Challenge,
|
||||||
ChallengeStatus,
|
ChallengeStatus,
|
||||||
Csr,
|
Csr,
|
||||||
Directory,
|
Directory,
|
||||||
|
@ -290,7 +291,7 @@ pub async fn site(args: ProcessorArgs<'_>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
TokioScope::scope_and_collect(|scope|{
|
TokioScope::scope_and_collect(|scope|{
|
||||||
for authorization in authorizations {
|
for authorization in authorizations {
|
||||||
scope.spawn(auth(authorization, args.challenge_dir(), args.dnsserver()));
|
scope.spawn(auth(authorization, args.challenge_dir(), args.dns_manager()));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -371,12 +372,48 @@ pub async fn site(args: ProcessorArgs<'_>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, dnsserver: Option<Dns>) {
|
async fn dns_auth(mut dns_challenge: Challenge, manager: Manager) -> types::Result<()> {
|
||||||
if let Some(_dnschallenge) = auth.get_challenge("dns-01") {
|
let token = match dns_challenge.token {
|
||||||
if let Some(_dnsserver) = dnsserver {
|
Some(ref token) => token.clone(),
|
||||||
} else {
|
None => {
|
||||||
debug!("DNS-01 is disabled")
|
return Error::err("Failed to get valid Token");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let value = match dns_challenge.key_authorization_encoded() {
|
||||||
|
Ok(Some(ref value)) => value.clone(),
|
||||||
|
Ok(None) => return Error::err("Failed to get key authoriration: No Authorization returned"),
|
||||||
|
Err(error) => return Error::err(format!("Failed to get key_authorization: {error}")),
|
||||||
|
};
|
||||||
|
if let Some(_guard) = manager.set_record(token.clone(), value).await {
|
||||||
|
dns_challenge = match dns_challenge.validate().await {
|
||||||
|
Ok(challenge) => challenge,
|
||||||
|
Err(error) => return Error::err(format!("Failed to send the request for validation: {error}")),
|
||||||
|
};
|
||||||
|
let challenge_result = dns_challenge.wait_done(WAIT_TIME, ATTEMPTS).await;
|
||||||
|
drop(_guard);
|
||||||
|
match challenge_result {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("Challenge: {token} was correctly validated");
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(error) => Error::err(format!("Failed to validate the challenge: {token}: {error}")),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Error::err("Failed to set record")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, manager: Manager) {
|
||||||
|
if let Some(dns_challenge) = auth.get_challenge("dns-01") {
|
||||||
|
match dns_auth(dns_challenge, manager).await {
|
||||||
|
Ok(()) => return,
|
||||||
|
Err(error) => {
|
||||||
|
error!("Failed to authenticate via DNS: {error}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("DNS-01 is disabled")
|
||||||
}
|
}
|
||||||
if !auth.wildcard.unwrap_or(false) {
|
if !auth.wildcard.unwrap_or(false) {
|
||||||
if let Some(mut challenge) = auth.get_challenge("http-01") {
|
if let Some(mut challenge) = auth.get_challenge("http-01") {
|
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
Algorithm,
|
Algorithm,
|
||||||
Strength,
|
Strength,
|
||||||
},
|
},
|
||||||
|
dns::DnsBuilder,
|
||||||
structs::Error,
|
structs::Error,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -15,6 +16,7 @@ use openssl::pkey::{
|
||||||
PKey,
|
PKey,
|
||||||
Private,
|
Private,
|
||||||
};
|
};
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
@ -22,7 +24,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct GeneralConfig {
|
pub struct GeneralConfig {
|
||||||
#[serde(default = "GeneralConfig::default_accounts")]
|
#[serde(default = "GeneralConfig::default_accounts")]
|
||||||
pub accounts_path: String,
|
pub accounts_path: String,
|
||||||
|
@ -31,7 +34,7 @@ pub struct GeneralConfig {
|
||||||
#[serde(default = "GeneralConfig::default_challenge")]
|
#[serde(default = "GeneralConfig::default_challenge")]
|
||||||
pub http_challenge_path: Option<String>,
|
pub http_challenge_path: Option<String>,
|
||||||
#[serde(default = "GeneralConfig::default_dns")]
|
#[serde(default = "GeneralConfig::default_dns")]
|
||||||
pub dns: Option<Dns>,
|
pub dns: HashMap<String, DnsBuilder>,
|
||||||
#[serde(default = "GeneralConfig::default_certificates")]
|
#[serde(default = "GeneralConfig::default_certificates")]
|
||||||
pub certificates_path: String,
|
pub certificates_path: String,
|
||||||
#[serde(default = "GeneralConfig::default_cas")]
|
#[serde(default = "GeneralConfig::default_cas")]
|
||||||
|
@ -55,8 +58,8 @@ impl GeneralConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn default_dns() -> Option<Dns> {
|
pub(super) fn default_dns() -> HashMap<String, DnsBuilder> {
|
||||||
None
|
HashMap::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -70,12 +73,10 @@ impl GeneralConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct Dns;
|
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Eab {
|
pub struct Eab {
|
||||||
#[serde(rename = "eab_token", alias = "id")]
|
#[serde(rename = "eab_token", alias = "id")]
|
||||||
pub token: String,
|
pub token: String,
|
||||||
|
@ -84,19 +85,22 @@ pub struct Eab {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eab {
|
impl Eab {
|
||||||
pub fn key(&self) -> Result<PKey<Private>, Error> {
|
pub fn key(&self) -> super::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".into()));
|
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}")))
|
PKey::hmac(decoded).map_err(|error| Error::new(format!("Failed to parse the private key: {error}")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct CA {
|
pub struct CA {
|
||||||
/// Url for the directory
|
/// Url for the directory
|
||||||
|
#[schemars(transform=crate::utils::schema::uri_transform)]
|
||||||
pub directory: String,
|
pub directory: String,
|
||||||
|
|
||||||
/// Email addresses for the CA to contact the user
|
/// Email addresses for the CA to contact the user
|
||||||
|
#[schemars(transform=crate::utils::schema::email_transform)]
|
||||||
pub email_addresses: Option<VString>,
|
pub email_addresses: Option<VString>,
|
||||||
|
|
||||||
#[serde(flatten, default)]
|
#[serde(flatten, default)]
|
||||||
|
@ -104,6 +108,7 @@ pub struct CA {
|
||||||
|
|
||||||
/// Amount of days the certificate is renewed before the Certificate is outdated
|
/// Amount of days the certificate is renewed before the Certificate is outdated
|
||||||
/// TODO: give to processor
|
/// TODO: give to processor
|
||||||
|
#[schemars(range(min = 1, max = 90))]
|
||||||
#[serde(default = "CA::default_renew")]
|
#[serde(default = "CA::default_renew")]
|
||||||
pub renew_before: u32,
|
pub renew_before: u32,
|
||||||
|
|
||||||
|
@ -115,18 +120,11 @@ impl CA {
|
||||||
fn default_renew() -> u32 {
|
fn default_renew() -> u32 {
|
||||||
7
|
7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_owner() -> String {
|
|
||||||
"root".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_group() -> String {
|
|
||||||
"root".into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct SiteConfig {
|
pub struct SiteConfig {
|
||||||
/// The Configured Certificate Authority
|
/// The Configured Certificate Authority
|
||||||
pub ca: String,
|
pub ca: String,
|
||||||
|
@ -140,6 +138,7 @@ pub struct SiteConfig {
|
||||||
|
|
||||||
/// EmailAdresses that this Certificate is valid for
|
/// EmailAdresses that this Certificate is valid for
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[schemars(transform=crate::utils::schema::email_transform)]
|
||||||
pub emails: VString,
|
pub emails: VString,
|
||||||
|
|
||||||
/// The systemd services are reloaded
|
/// The systemd services are reloaded
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
use crate::{
|
||||||
|
consts::RsaStrength,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
use macro_rules_attribute::macro_rules_derive;
|
use macro_rules_attribute::macro_rules_derive;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
#[derive(Copy, Deserialize, Default)]
|
#[derive(Copy, Deserialize, Default, JsonSchema)]
|
||||||
pub enum Algorithm {
|
pub enum Algorithm {
|
||||||
Rsa,
|
Rsa,
|
||||||
Brainpool,
|
Brainpool,
|
||||||
|
@ -16,7 +19,8 @@ pub enum Algorithm {
|
||||||
|
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
#[derive(Copy, Deserialize, Default)]
|
#[derive(Copy, Deserialize, Default, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub enum Strength {
|
pub enum Strength {
|
||||||
Weak,
|
Weak,
|
||||||
Middle,
|
Middle,
|
||||||
|
@ -26,6 +30,10 @@ pub enum Strength {
|
||||||
|
|
||||||
impl Strength {
|
impl Strength {
|
||||||
pub fn rsabits(self) -> u32 {
|
pub fn rsabits(self) -> u32 {
|
||||||
self as u32
|
match self {
|
||||||
|
Self::Weak => RsaStrength::Weak as u32,
|
||||||
|
Self::Middle => RsaStrength::Middle as u32,
|
||||||
|
Self::Strong => RsaStrength::Strong as u32,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
src/types/dns/dnsupdate.rs
Normale Datei
36
src/types/dns/dnsupdate.rs
Normale Datei
|
@ -0,0 +1,36 @@
|
||||||
|
use macro_rules_attribute::macro_rules_derive;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
macros::DefDer,
|
||||||
|
types::traits::{
|
||||||
|
DnsHandler,
|
||||||
|
DnsToken,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Deserialize, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct DNSUpdateClientOptions {}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
pub(super) struct DnsUpdateHandler {}
|
||||||
|
|
||||||
|
impl DnsHandler for DnsUpdateHandler {
|
||||||
|
async fn set_record(&self, _domain: String, _content: String) -> crate::types::BoxedResult<dyn DnsToken> {
|
||||||
|
Ok(Box::pin(DnsUpdateToken {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
struct DnsUpdateToken {}
|
||||||
|
|
||||||
|
impl Drop for DnsUpdateToken {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DnsToken for DnsUpdateToken {}
|
||||||
|
unsafe impl Send for DnsUpdateToken {}
|
109
src/types/dns/mod.rs
Normale Datei
109
src/types/dns/mod.rs
Normale Datei
|
@ -0,0 +1,109 @@
|
||||||
|
pub(super) mod dnsupdate;
|
||||||
|
pub(super) mod pdns;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
macros::DefDer,
|
||||||
|
types::{
|
||||||
|
dns::{
|
||||||
|
dnsupdate::{
|
||||||
|
DNSUpdateClientOptions,
|
||||||
|
DnsUpdateHandler,
|
||||||
|
},
|
||||||
|
pdns::{
|
||||||
|
PdnsClientOptions,
|
||||||
|
PdnsHandler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
structs::Error,
|
||||||
|
traits::{
|
||||||
|
DnsHandler,
|
||||||
|
DnsToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use log::*;
|
||||||
|
use macro_rules_attribute::macro_rules_derive;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
pub struct Manager(Arc<Mutex<InnerManager>>);
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(InnerManager {
|
||||||
|
servers: HashMap::new(),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_record(&self, domain: String, value: String) -> Option<Pin<Box<dyn DnsToken + '_>>> {
|
||||||
|
let mut tld = domain.clone();
|
||||||
|
if !tld.ends_with('.') {
|
||||||
|
tld.push('.');
|
||||||
|
}
|
||||||
|
let (mut best_match_domain, mut best_match_length) = ("", 0);
|
||||||
|
let guard = self.0.lock().await;
|
||||||
|
for domain in guard.servers.keys() {
|
||||||
|
if domain.ends_with(&tld) {
|
||||||
|
let matched = domain.rmatches(&tld).last().unwrap();
|
||||||
|
if matched.len() > best_match_length {
|
||||||
|
best_match_domain = matched;
|
||||||
|
best_match_length = matched.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if best_match_length == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let handler = guard.servers.get(best_match_domain).unwrap();
|
||||||
|
match handler.set_record(domain, value).await {
|
||||||
|
Ok(token) => Some(token),
|
||||||
|
Err(error) => {
|
||||||
|
error!("Failed to set the DNS Record in the backend: {error}");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Manager {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InnerManager {
|
||||||
|
servers: HashMap<String, Dns>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Deserialize, Default, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
#[serde(tag = "type", rename_all = "lowercase")]
|
||||||
|
pub enum DnsBuilder {
|
||||||
|
PowerDNS(PdnsClientOptions),
|
||||||
|
DNSUpdate(DNSUpdateClientOptions),
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
enum Dns {
|
||||||
|
PowerDNS(PdnsHandler),
|
||||||
|
DNSUpdate(DnsUpdateHandler),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dns {
|
||||||
|
pub async fn set_record(&self, domain: String, content: String) -> crate::types::BoxedResult<dyn 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,
|
||||||
|
Dns::None => Error::err("Not Implemented"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
205
src/types/dns/pdns.rs
Normale Datei
205
src/types/dns/pdns.rs
Normale Datei
|
@ -0,0 +1,205 @@
|
||||||
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
time::{
|
||||||
|
SystemTime,
|
||||||
|
UNIX_EPOCH,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use derive_new::new;
|
||||||
|
use log::*;
|
||||||
|
use macro_rules_attribute::macro_rules_derive;
|
||||||
|
use reqwest::{
|
||||||
|
RequestBuilder,
|
||||||
|
StatusCode,
|
||||||
|
};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
|
use tokio::runtime::Handle;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
macros::DefDer,
|
||||||
|
types::{
|
||||||
|
structs::Error,
|
||||||
|
traits::{
|
||||||
|
DnsHandler,
|
||||||
|
DnsToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Deserialize, JsonSchema)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct PdnsClientOptions {
|
||||||
|
api_key: String,
|
||||||
|
server: String,
|
||||||
|
#[serde(default = "PdnsClientOptions::default_serverid")]
|
||||||
|
server_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl PdnsClientOptions {
|
||||||
|
fn default_serverid() -> String {
|
||||||
|
"localhost".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
pub(super) struct PdnsHandler {
|
||||||
|
client: reqwest::Client,
|
||||||
|
server: String,
|
||||||
|
api_key: String,
|
||||||
|
server_id: String,
|
||||||
|
zone: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DnsHandler for PdnsHandler {
|
||||||
|
async fn set_record(&self, domain: String, content: String) -> crate::types::BoxedResult<dyn DnsToken> {
|
||||||
|
let base_request = self
|
||||||
|
.client
|
||||||
|
.patch(format!("https://{}/api/v1/servers/{}/zones/{}", self.server, self.server_id, self.zone))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("X-API-Key", self.api_key.clone());
|
||||||
|
|
||||||
|
match base_request
|
||||||
|
.try_clone()
|
||||||
|
.unwrap()
|
||||||
|
.json(&RecordUpdate::new(vec![
|
||||||
|
RRSet::new(
|
||||||
|
domain,
|
||||||
|
"TXT",
|
||||||
|
ChangeType::Replace {
|
||||||
|
records: vec![Record::new(content)],
|
||||||
|
comments: vec![Comment::new("ACME entry", "")],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_resp) if _resp.status() == StatusCode::NO_CONTENT => {},
|
||||||
|
Ok(resp) => {
|
||||||
|
let err = match resp.json::<PdnsError>().await {
|
||||||
|
Ok(error) => error.error,
|
||||||
|
Err(error) => format!("{error}"),
|
||||||
|
};
|
||||||
|
return Error::err(format!("Failed to set the record: {err}"));
|
||||||
|
},
|
||||||
|
Err(error) => return Error::err(format!("Failed to send the request to update the record: {error}")),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::pin(PdnsToken {
|
||||||
|
builder: base_request.try_clone().unwrap().json(&0),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Token that deletes the Record when its no longer needed
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct PdnsToken {
|
||||||
|
builder: RequestBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PdnsToken {
|
||||||
|
fn new(builder: RequestBuilder) -> Pin<Box<Self>> {
|
||||||
|
Box::pin(Self {
|
||||||
|
builder,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for PdnsToken {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let handle = match Handle::try_current() {
|
||||||
|
Ok(handle) => handle,
|
||||||
|
Err(error) => {
|
||||||
|
error!("Failed to aquire the handle from tokio. Cleanup of the Token Failed: {error}");
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
match handle.block_on(self.builder.try_clone().unwrap().send()) {
|
||||||
|
Ok(response) => {
|
||||||
|
if response.status() != StatusCode::NO_CONTENT {
|
||||||
|
let status = response.status();
|
||||||
|
match handle.block_on(response.json::<PdnsError>()) {
|
||||||
|
Ok(error) => error!("Failed to delete the Record({status}): {error}"),
|
||||||
|
Err(error) => error!("Failed to parse the Error Response({status}): {error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(error) => error!("Failed to delete the Record: {error}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for PdnsToken {}
|
||||||
|
impl DnsToken for PdnsToken {}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct PdnsError {
|
||||||
|
pub error: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub errors: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Serialize, new)]
|
||||||
|
struct Comment {
|
||||||
|
#[new(into)]
|
||||||
|
content: String,
|
||||||
|
#[new(into)]
|
||||||
|
account: String,
|
||||||
|
|
||||||
|
#[new(value = "Comment::unix_epoch()")]
|
||||||
|
modified_at: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Comment {
|
||||||
|
#[inline]
|
||||||
|
fn unix_epoch() -> u64 {
|
||||||
|
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Serialize, new)]
|
||||||
|
struct Record {
|
||||||
|
content: String,
|
||||||
|
#[new(value = "false")]
|
||||||
|
disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(tag = "changetype", rename_all = "UPPERCASE")]
|
||||||
|
enum ChangeType {
|
||||||
|
Replace {
|
||||||
|
records: Vec<Record>,
|
||||||
|
comments: Vec<Comment>,
|
||||||
|
},
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Serialize, new)]
|
||||||
|
struct RRSet {
|
||||||
|
name: String,
|
||||||
|
#[new(into)]
|
||||||
|
r#type: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
changetype: ChangeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Serialize, new)]
|
||||||
|
struct RecordUpdate {
|
||||||
|
rrset: Vec<RRSet>,
|
||||||
|
}
|
|
@ -6,8 +6,9 @@ use std::{
|
||||||
use acme2_eab::Identifier;
|
use acme2_eab::Identifier;
|
||||||
use openssl::x509::GeneralName;
|
use openssl::x509::GeneralName;
|
||||||
|
|
||||||
use super::{
|
use crate::types::{
|
||||||
config::GeneralConfig,
|
config::GeneralConfig,
|
||||||
|
dns::pdns::PdnsError,
|
||||||
structs::{
|
structs::{
|
||||||
Error,
|
Error,
|
||||||
San,
|
San,
|
||||||
|
@ -78,6 +79,30 @@ impl From<San> for Identifier {
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(&self.0)
|
f.write_str(&self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Display for PdnsError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.error)?;
|
||||||
|
if !self.errors.is_empty() {
|
||||||
|
f.write_str("(")?;
|
||||||
|
let mut iter = self.errors.iter();
|
||||||
|
let mut error = iter.next().unwrap();
|
||||||
|
loop {
|
||||||
|
f.write_str(error)?;
|
||||||
|
match iter.next() {
|
||||||
|
Some(err) => {
|
||||||
|
error = err;
|
||||||
|
f.write_str(", ")?;
|
||||||
|
},
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_str(")")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
use std::collections::HashSet;
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
pin::Pin,
|
||||||
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::types::traits::DnsToken;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod cryptography;
|
pub mod cryptography;
|
||||||
|
pub mod dns;
|
||||||
mod foreign_impl;
|
mod foreign_impl;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
@ -12,3 +18,8 @@ pub type VString = Vec<String>;
|
||||||
|
|
||||||
/// Alias for an Safe Hashset
|
/// Alias for an Safe Hashset
|
||||||
pub type SafeSet<T> = Mutex<HashSet<T>>;
|
pub type SafeSet<T> = Mutex<HashSet<T>>;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, structs::Error>;
|
||||||
|
|
||||||
|
#[allow(type_alias_bounds)]
|
||||||
|
pub type BoxedResult<T: DnsToken> = std::result::Result<Pin<Box<T>>, structs::Error>;
|
||||||
|
|
|
@ -10,8 +10,13 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use acme2_eab::Account;
|
use acme2_eab::Account;
|
||||||
use clap::Parser;
|
use clap::{
|
||||||
|
Parser,
|
||||||
|
Subcommand,
|
||||||
|
};
|
||||||
|
use derive_new::new;
|
||||||
use macro_rules_attribute::macro_rules_derive;
|
use macro_rules_attribute::macro_rules_derive;
|
||||||
|
use reqwest::Client;
|
||||||
use tokio::sync::MutexGuard;
|
use tokio::sync::MutexGuard;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -23,28 +28,39 @@ use crate::{
|
||||||
Algorithm,
|
Algorithm,
|
||||||
Strength,
|
Strength,
|
||||||
},
|
},
|
||||||
|
dns::Manager,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::types::config::Dns;
|
|
||||||
|
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct Arguments {
|
pub struct Arguments {
|
||||||
pub config: String,
|
pub config: String,
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub subcommands: Option<SubCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum SubCommand {
|
||||||
|
Schema,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_rules_derive(DefDer)]
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[derive(new)]
|
||||||
pub struct ProcessorArgs<'a> {
|
pub struct ProcessorArgs<'a> {
|
||||||
site: SiteConfig,
|
site: SiteConfig,
|
||||||
account: Arc<Account>,
|
account: Arc<Account>,
|
||||||
reload_services: &'a SafeSet<String>,
|
reload_services: &'a SafeSet<String>,
|
||||||
restart_services: &'a SafeSet<String>,
|
restart_services: &'a SafeSet<String>,
|
||||||
certificate_dir: PathBuf,
|
certificate_dir: PathBuf,
|
||||||
|
#[new(value = "7")]
|
||||||
refresh_time: u32,
|
refresh_time: u32,
|
||||||
challenge_dir: Option<PathBuf>,
|
challenge_dir: Option<PathBuf>,
|
||||||
dnsserver: Option<Dns>,
|
dns_manager: Manager,
|
||||||
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a: 'b, 'b> ProcessorArgs<'a> {
|
impl<'a: 'b, 'b> ProcessorArgs<'a> {
|
||||||
|
@ -70,32 +86,13 @@ impl<'a: 'b, 'b> ProcessorArgs<'a> {
|
||||||
|
|
||||||
attr_function!(pub challenge_dir => Option<PathBuf>);
|
attr_function!(pub challenge_dir => Option<PathBuf>);
|
||||||
|
|
||||||
attr_function!(pub dnsserver => Option<Dns>);
|
|
||||||
|
|
||||||
attr_function!(pub owner site => String);
|
attr_function!(pub owner site => String);
|
||||||
|
|
||||||
attr_function!(pub group site => String);
|
attr_function!(pub group site => String);
|
||||||
|
|
||||||
pub fn new(
|
attr_function!(pub client => Client);
|
||||||
site: SiteConfig,
|
|
||||||
account: Arc<Account>,
|
attr_function!(pub dns_manager => Manager);
|
||||||
reload_services: &'a SafeSet<String>,
|
|
||||||
restart_services: &'a SafeSet<String>,
|
|
||||||
certificate_dir: PathBuf,
|
|
||||||
http_challenge_dir: Option<PathBuf>,
|
|
||||||
dnsserver: Option<Dns>,
|
|
||||||
) -> Self {
|
|
||||||
ProcessorArgs {
|
|
||||||
site,
|
|
||||||
account,
|
|
||||||
reload_services,
|
|
||||||
restart_services,
|
|
||||||
certificate_dir,
|
|
||||||
refresh_time: 7,
|
|
||||||
challenge_dir: http_challenge_dir,
|
|
||||||
dnsserver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn account(&self) -> Arc<Account> {
|
pub fn account(&self) -> Arc<Account> {
|
||||||
Arc::clone(&self.account)
|
Arc::clone(&self.account)
|
||||||
|
@ -133,26 +130,26 @@ pub enum San {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_rules_derive(DefDer)]
|
#[macro_rules_derive(DefDer)]
|
||||||
pub struct Error(pub(super) String);
|
#[derive(derive_new::new)]
|
||||||
|
pub struct Error {
|
||||||
|
pub(super) message: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[allow(unused)]
|
||||||
pub fn from_display<T: Display>(input: T) -> Error {
|
pub fn from_display<T: Display>(input: T) -> Error {
|
||||||
Error::new(format!("{input}"))
|
Error::new(format!("{input}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[allow(unused)]
|
||||||
pub fn from_debug<T: Debug>(input: T) -> Error {
|
pub fn from_debug<T: Debug>(input: T) -> Error {
|
||||||
Error::new(format!("{input:?}"))
|
Error::new(format!("{input:?}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn err<T>(message: String) -> Result<T, Self> {
|
pub fn err<T, M: Into<String>>(message: M) -> Result<T, Self> {
|
||||||
Err(Self::new(message))
|
Err(Self::new(message.into()))
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn new(message: String) -> Self {
|
|
||||||
Self(message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,18 @@ use crate::consts::{
|
||||||
SECP_STRONG,
|
SECP_STRONG,
|
||||||
SECP_WEAK,
|
SECP_WEAK,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
types,
|
||||||
|
types::{
|
||||||
|
cryptography::{
|
||||||
|
Algorithm,
|
||||||
|
Strength,
|
||||||
|
},
|
||||||
|
structs::San,
|
||||||
|
},
|
||||||
|
};
|
||||||
use log::*;
|
use log::*;
|
||||||
use openssl::{
|
use openssl::{
|
||||||
asn1::Asn1Time,
|
asn1::Asn1Time,
|
||||||
|
@ -24,14 +36,6 @@ use tokio::{
|
||||||
io::AsyncReadExt,
|
io::AsyncReadExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
|
||||||
cryptography::{
|
|
||||||
Algorithm,
|
|
||||||
Strength,
|
|
||||||
},
|
|
||||||
structs::San,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait FromFile: Default + DeserializeOwned {
|
pub trait FromFile: Default + DeserializeOwned {
|
||||||
async fn from_file(file: File) -> Self;
|
async fn from_file(file: File) -> Self;
|
||||||
}
|
}
|
||||||
|
@ -115,3 +119,10 @@ impl MatchX509 for X509 {
|
||||||
config_san.difference(&cert_san).count() == 0
|
config_san.difference(&cert_san).count() == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(drop_bounds)]
|
||||||
|
pub trait DnsToken: std::fmt::Debug + Send + Drop {}
|
||||||
|
|
||||||
|
pub trait DnsHandler: std::fmt::Debug + Send {
|
||||||
|
async fn set_record(&self, domain: String, content: String) -> types::BoxedResult<dyn DnsToken>;
|
||||||
|
}
|
||||||
|
|
82
src/utils.rs
82
src/utils.rs
|
@ -20,6 +20,7 @@ use crate::{
|
||||||
use caps::{
|
use caps::{
|
||||||
CapSet,
|
CapSet,
|
||||||
has_cap,
|
has_cap,
|
||||||
|
read,
|
||||||
};
|
};
|
||||||
use log::*;
|
use log::*;
|
||||||
use openssl::{
|
use openssl::{
|
||||||
|
@ -37,6 +38,8 @@ use openssl::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CAPABILITY_SET: CapSet = CapSet::Permitted;
|
||||||
|
|
||||||
pub fn prefix_emails(input: Vec<String>) -> Vec<String> {
|
pub fn prefix_emails(input: Vec<String>) -> Vec<String> {
|
||||||
let mut output = Vec::with_capacity(input.len());
|
let mut output = Vec::with_capacity(input.len());
|
||||||
for mut addr in input {
|
for mut addr in input {
|
||||||
|
@ -105,8 +108,27 @@ pub fn string_to_cn(name: String) -> X509Name {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_permissions() -> bool {
|
pub fn check_permissions() -> bool {
|
||||||
has_cap(None, CapSet::Ambient, caps::Capability::CAP_CHOWN).unwrap_or(false) &&
|
let caps = has_cap(None, CAPABILITY_SET, caps::Capability::CAP_CHOWN).unwrap_or(false) &&
|
||||||
has_cap(None, CapSet::Ambient, caps::Capability::CAP_FOWNER).unwrap_or(false)
|
has_cap(None, CAPABILITY_SET, caps::Capability::CAP_FOWNER).unwrap_or(false);
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
for set in [
|
||||||
|
CapSet::Ambient,
|
||||||
|
CapSet::Bounding,
|
||||||
|
CapSet::Effective,
|
||||||
|
CapSet::Inheritable,
|
||||||
|
CapSet::Permitted,
|
||||||
|
] {
|
||||||
|
match read(None, set) {
|
||||||
|
Ok(current) => {
|
||||||
|
trace!("Current {set:?} Set: {current:?}")
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
trace!("Failed to get the current {set:?} Set: {error}")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
caps
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_uid_gid(username: String, group: String) -> (Option<u32>, Option<u32>) {
|
pub fn get_uid_gid(username: String, group: String) -> (Option<u32>, Option<u32>) {
|
||||||
|
@ -156,3 +178,59 @@ pub fn get_uid_gid(username: String, group: String) -> (Option<u32>, Option<u32>
|
||||||
};
|
};
|
||||||
(uid, gid)
|
(uid, gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod schema {
|
||||||
|
|
||||||
|
use schemars::Schema;
|
||||||
|
use serde_json::{
|
||||||
|
Map,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn is_array(val: &Value) -> bool {
|
||||||
|
if let Some(typ) = val.as_str() {
|
||||||
|
typ == "array"
|
||||||
|
} else if let Some(typ) = val.as_array() {
|
||||||
|
for typvariant in typ {
|
||||||
|
if is_array(typvariant) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_transform(schema: &mut Schema, typ: &'static str) {
|
||||||
|
let typval = Value::String(typ.into());
|
||||||
|
if let Some(val) = schema.get("type") {
|
||||||
|
if is_array(val) {
|
||||||
|
if let Some(items) = schema.get("items") {
|
||||||
|
let mut items = items.to_owned();
|
||||||
|
items.as_object_mut().unwrap().insert("type".to_string(), typval);
|
||||||
|
schema.insert("items".to_owned(), items);
|
||||||
|
} else {
|
||||||
|
schema.insert(
|
||||||
|
"items".to_owned(),
|
||||||
|
Value::Object({
|
||||||
|
let mut items = Map::with_capacity(1);
|
||||||
|
items.insert("type".to_owned(), typval);
|
||||||
|
items
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schema.insert("type".to_string(), typval);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn email_transform(schema: &mut Schema) {
|
||||||
|
type_transform(schema, "email");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uri_transform(schema: &mut Schema) {
|
||||||
|
type_transform(schema, "uri");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren