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.'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 = []
|
||||
|
||||
[dependencies]
|
||||
derive-new = "0.7.0"
|
||||
env_logger = "0.11"
|
||||
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"
|
||||
[dependencies.caps]
|
||||
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
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::clone_on_copy)]
|
||||
#![allow(clippy::identity_op)]
|
||||
#![allow(refining_impl_trait)]
|
||||
#![allow(clippy::collapsible_if)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub(crate) mod consts;
|
||||
pub(crate) mod macros;
|
||||
|
@ -18,11 +20,15 @@ use crate::{
|
|||
GeneralConfig,
|
||||
SiteConfig,
|
||||
},
|
||||
dns::Manager,
|
||||
structs::{
|
||||
Arguments,
|
||||
Error,
|
||||
ProcessorArgs,
|
||||
SubCommand,
|
||||
},
|
||||
},
|
||||
utils::check_permissions,
|
||||
};
|
||||
use acme2_eab::Directory;
|
||||
use async_scoped::TokioScope;
|
||||
|
@ -38,6 +44,16 @@ use openssl::{
|
|||
},
|
||||
};
|
||||
use reqwest::tls::Version;
|
||||
use schemars::{
|
||||
SchemaGenerator,
|
||||
consts::meta_schemas::DRAFT07,
|
||||
generate::SchemaSettings,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::ser::{
|
||||
Formatter,
|
||||
PrettyFormatter,
|
||||
};
|
||||
use std::{
|
||||
collections::{
|
||||
HashMap,
|
||||
|
@ -57,15 +73,16 @@ use tokio::{
|
|||
create_dir_all,
|
||||
read_dir,
|
||||
},
|
||||
io::AsyncReadExt,
|
||||
io::{
|
||||
AsyncReadExt,
|
||||
AsyncWriteExt,
|
||||
},
|
||||
sync::Mutex,
|
||||
};
|
||||
use tokio_stream::{
|
||||
StreamExt,
|
||||
wrappers::ReadDirStream,
|
||||
};
|
||||
use types::structs::Error;
|
||||
use utils::check_permissions;
|
||||
|
||||
|
||||
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> {
|
||||
let client = default_client()?;
|
||||
let dns_manager = Manager::new();
|
||||
let systemd_access = daemon::booted();
|
||||
let mainconfig = {
|
||||
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 dnsserver = None;
|
||||
|
||||
unsafe {
|
||||
TokioScope::scope_and_collect(|scope| {
|
||||
|
@ -164,7 +181,8 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
|||
&restart_services,
|
||||
certs.clone(),
|
||||
challengepath.clone(),
|
||||
dnsserver.clone(),
|
||||
dns_manager.clone(),
|
||||
client.clone(),
|
||||
)));
|
||||
} else {
|
||||
error!("Could not process site {} because of previous errors", site.name)
|
||||
|
@ -180,9 +198,50 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
|
|||
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() {
|
||||
log_init();
|
||||
if !check_permissions() {
|
||||
let args = Arguments::parse();
|
||||
if args.subcommands.is_none() && !check_permissions() {
|
||||
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)"
|
||||
);
|
||||
|
@ -195,7 +254,10 @@ fn main() {
|
|||
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));
|
||||
if let Err(error) = result {
|
||||
error!("{error}");
|
||||
|
|
|
@ -25,12 +25,12 @@ use crate::{
|
|||
load_privkey,
|
||||
prelude::*,
|
||||
types::{
|
||||
config::{
|
||||
CA,
|
||||
Dns,
|
||||
},
|
||||
self,
|
||||
config::CA,
|
||||
cryptography::Algorithm,
|
||||
dns::Manager,
|
||||
structs::{
|
||||
Error,
|
||||
ProcessorArgs,
|
||||
San,
|
||||
},
|
||||
|
@ -45,6 +45,7 @@ use acme2_eab::{
|
|||
Account,
|
||||
AccountBuilder,
|
||||
Authorization,
|
||||
Challenge,
|
||||
ChallengeStatus,
|
||||
Csr,
|
||||
Directory,
|
||||
|
@ -290,7 +291,7 @@ pub async fn site(args: ProcessorArgs<'_>) {
|
|||
unsafe {
|
||||
TokioScope::scope_and_collect(|scope|{
|
||||
for authorization in authorizations {
|
||||
scope.spawn(auth(authorization, args.challenge_dir(), args.dnsserver()));
|
||||
scope.spawn(auth(authorization, args.challenge_dir(), args.dns_manager()));
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -371,13 +372,49 @@ pub async fn site(args: ProcessorArgs<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, dnsserver: Option<Dns>) {
|
||||
if let Some(_dnschallenge) = auth.get_challenge("dns-01") {
|
||||
if let Some(_dnsserver) = dnsserver {
|
||||
async fn dns_auth(mut dns_challenge: Challenge, manager: Manager) -> types::Result<()> {
|
||||
let token = match dns_challenge.token {
|
||||
Some(ref token) => token.clone(),
|
||||
None => {
|
||||
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 let Some(mut challenge) = auth.get_challenge("http-01") {
|
||||
trace!("CA has an http-challenge");
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
Algorithm,
|
||||
Strength,
|
||||
},
|
||||
dns::DnsBuilder,
|
||||
structs::Error,
|
||||
},
|
||||
};
|
||||
|
@ -15,6 +16,7 @@ use openssl::pkey::{
|
|||
PKey,
|
||||
Private,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
@ -22,7 +24,8 @@ use std::{
|
|||
};
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GeneralConfig {
|
||||
#[serde(default = "GeneralConfig::default_accounts")]
|
||||
pub accounts_path: String,
|
||||
|
@ -31,7 +34,7 @@ pub struct GeneralConfig {
|
|||
#[serde(default = "GeneralConfig::default_challenge")]
|
||||
pub http_challenge_path: Option<String>,
|
||||
#[serde(default = "GeneralConfig::default_dns")]
|
||||
pub dns: Option<Dns>,
|
||||
pub dns: HashMap<String, DnsBuilder>,
|
||||
#[serde(default = "GeneralConfig::default_certificates")]
|
||||
pub certificates_path: String,
|
||||
#[serde(default = "GeneralConfig::default_cas")]
|
||||
|
@ -55,8 +58,8 @@ impl GeneralConfig {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn default_dns() -> Option<Dns> {
|
||||
None
|
||||
pub(super) fn default_dns() -> HashMap<String, DnsBuilder> {
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -70,12 +73,10 @@ impl GeneralConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Dns;
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Eab {
|
||||
#[serde(rename = "eab_token", alias = "id")]
|
||||
pub token: String,
|
||||
|
@ -84,19 +85,22 @@ pub struct Eab {
|
|||
}
|
||||
|
||||
impl Eab {
|
||||
pub fn key(&self) -> Result<PKey<Private>, Error> {
|
||||
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()));
|
||||
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"));
|
||||
PKey::hmac(decoded).map_err(|error| Error::new(format!("Failed to parse the private key: {error}")))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CA {
|
||||
/// Url for the directory
|
||||
#[schemars(transform=crate::utils::schema::uri_transform)]
|
||||
pub directory: String,
|
||||
|
||||
/// Email addresses for the CA to contact the user
|
||||
#[schemars(transform=crate::utils::schema::email_transform)]
|
||||
pub email_addresses: Option<VString>,
|
||||
|
||||
#[serde(flatten, default)]
|
||||
|
@ -104,6 +108,7 @@ pub struct CA {
|
|||
|
||||
/// Amount of days the certificate is renewed before the Certificate is outdated
|
||||
/// TODO: give to processor
|
||||
#[schemars(range(min = 1, max = 90))]
|
||||
#[serde(default = "CA::default_renew")]
|
||||
pub renew_before: u32,
|
||||
|
||||
|
@ -115,18 +120,11 @@ impl CA {
|
|||
fn default_renew() -> u32 {
|
||||
7
|
||||
}
|
||||
|
||||
fn default_owner() -> String {
|
||||
"root".into()
|
||||
}
|
||||
|
||||
fn default_group() -> String {
|
||||
"root".into()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Deserialize, Default)]
|
||||
#[derive(Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SiteConfig {
|
||||
/// The Configured Certificate Authority
|
||||
pub ca: String,
|
||||
|
@ -140,6 +138,7 @@ pub struct SiteConfig {
|
|||
|
||||
/// EmailAdresses that this Certificate is valid for
|
||||
#[serde(default)]
|
||||
#[schemars(transform=crate::utils::schema::email_transform)]
|
||||
pub emails: VString,
|
||||
|
||||
/// The systemd services are reloaded
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
use crate::{
|
||||
consts::RsaStrength,
|
||||
prelude::*,
|
||||
};
|
||||
use macro_rules_attribute::macro_rules_derive;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Copy, Deserialize, Default)]
|
||||
#[derive(Copy, Deserialize, Default, JsonSchema)]
|
||||
pub enum Algorithm {
|
||||
Rsa,
|
||||
Brainpool,
|
||||
|
@ -16,7 +19,8 @@ pub enum Algorithm {
|
|||
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Copy, Deserialize, Default)]
|
||||
#[derive(Copy, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum Strength {
|
||||
Weak,
|
||||
Middle,
|
||||
|
@ -26,6 +30,10 @@ pub enum Strength {
|
|||
|
||||
impl Strength {
|
||||
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 openssl::x509::GeneralName;
|
||||
|
||||
use super::{
|
||||
use crate::types::{
|
||||
config::GeneralConfig,
|
||||
dns::pdns::PdnsError,
|
||||
structs::{
|
||||
Error,
|
||||
San,
|
||||
|
@ -78,6 +79,30 @@ impl From<San> for Identifier {
|
|||
|
||||
impl Display for Error {
|
||||
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 crate::types::traits::DnsToken;
|
||||
|
||||
pub mod config;
|
||||
pub mod cryptography;
|
||||
pub mod dns;
|
||||
mod foreign_impl;
|
||||
pub mod structs;
|
||||
pub mod traits;
|
||||
|
@ -12,3 +18,8 @@ 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>;
|
||||
|
||||
#[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 clap::Parser;
|
||||
use clap::{
|
||||
Parser,
|
||||
Subcommand,
|
||||
};
|
||||
use derive_new::new;
|
||||
use macro_rules_attribute::macro_rules_derive;
|
||||
use reqwest::Client;
|
||||
use tokio::sync::MutexGuard;
|
||||
|
||||
use crate::{
|
||||
|
@ -23,28 +28,39 @@ use crate::{
|
|||
Algorithm,
|
||||
Strength,
|
||||
},
|
||||
dns::Manager,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::types::config::Dns;
|
||||
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
#[derive(Parser)]
|
||||
pub struct Arguments {
|
||||
pub config: String,
|
||||
#[command(subcommand)]
|
||||
pub subcommands: Option<SubCommand>,
|
||||
}
|
||||
|
||||
#[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> {
|
||||
site: SiteConfig,
|
||||
account: Arc<Account>,
|
||||
reload_services: &'a SafeSet<String>,
|
||||
restart_services: &'a SafeSet<String>,
|
||||
certificate_dir: PathBuf,
|
||||
#[new(value = "7")]
|
||||
refresh_time: u32,
|
||||
challenge_dir: Option<PathBuf>,
|
||||
dnsserver: Option<Dns>,
|
||||
dns_manager: Manager,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
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 dnsserver => Option<Dns>);
|
||||
|
||||
attr_function!(pub owner site => String);
|
||||
|
||||
attr_function!(pub group site => String);
|
||||
|
||||
pub fn new(
|
||||
site: SiteConfig,
|
||||
account: Arc<Account>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
attr_function!(pub client => Client);
|
||||
|
||||
attr_function!(pub dns_manager => Manager);
|
||||
|
||||
pub fn account(&self) -> Arc<Account> {
|
||||
Arc::clone(&self.account)
|
||||
|
@ -133,26 +130,26 @@ pub enum San {
|
|||
}
|
||||
|
||||
#[macro_rules_derive(DefDer)]
|
||||
pub struct Error(pub(super) String);
|
||||
#[derive(derive_new::new)]
|
||||
pub struct Error {
|
||||
pub(super) message: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
#[inline]
|
||||
#[allow(unused)]
|
||||
pub fn from_display<T: Display>(input: T) -> Error {
|
||||
Error::new(format!("{input}"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(unused)]
|
||||
pub fn from_debug<T: Debug>(input: T) -> Error {
|
||||
Error::new(format!("{input:?}"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn err<T>(message: String) -> Result<T, Self> {
|
||||
Err(Self::new(message))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new(message: String) -> Self {
|
||||
Self(message)
|
||||
pub fn err<T, M: Into<String>>(message: M) -> Result<T, Self> {
|
||||
Err(Self::new(message.into()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,18 @@ use crate::consts::{
|
|||
SECP_STRONG,
|
||||
SECP_WEAK,
|
||||
};
|
||||
|
||||
|
||||
use crate::{
|
||||
types,
|
||||
types::{
|
||||
cryptography::{
|
||||
Algorithm,
|
||||
Strength,
|
||||
},
|
||||
structs::San,
|
||||
},
|
||||
};
|
||||
use log::*;
|
||||
use openssl::{
|
||||
asn1::Asn1Time,
|
||||
|
@ -24,14 +36,6 @@ use tokio::{
|
|||
io::AsyncReadExt,
|
||||
};
|
||||
|
||||
use super::{
|
||||
cryptography::{
|
||||
Algorithm,
|
||||
Strength,
|
||||
},
|
||||
structs::San,
|
||||
};
|
||||
|
||||
pub trait FromFile: Default + DeserializeOwned {
|
||||
async fn from_file(file: File) -> Self;
|
||||
}
|
||||
|
@ -115,3 +119,10 @@ impl MatchX509 for X509 {
|
|||
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::{
|
||||
CapSet,
|
||||
has_cap,
|
||||
read,
|
||||
};
|
||||
use log::*;
|
||||
use openssl::{
|
||||
|
@ -37,6 +38,8 @@ use openssl::{
|
|||
},
|
||||
};
|
||||
|
||||
const CAPABILITY_SET: CapSet = CapSet::Permitted;
|
||||
|
||||
pub fn prefix_emails(input: Vec<String>) -> Vec<String> {
|
||||
let mut output = Vec::with_capacity(input.len());
|
||||
for mut addr in input {
|
||||
|
@ -105,8 +108,27 @@ pub fn string_to_cn(name: String) -> X509Name {
|
|||
}
|
||||
|
||||
pub fn check_permissions() -> bool {
|
||||
has_cap(None, CapSet::Ambient, caps::Capability::CAP_CHOWN).unwrap_or(false) &&
|
||||
has_cap(None, CapSet::Ambient, caps::Capability::CAP_FOWNER).unwrap_or(false)
|
||||
let caps = has_cap(None, CAPABILITY_SET, caps::Capability::CAP_CHOWN).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>) {
|
||||
|
@ -156,3 +178,59 @@ pub fn get_uid_gid(username: String, group: String) -> (Option<u32>, Option<u32>
|
|||
};
|
||||
(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