racme can now change the ownership of files

Dieser Commit ist enthalten in:
Sebastian Tobie 2025-05-17 08:55:11 +02:00
Ursprung 01a4a64ae6
Commit b8fca09327
8 geänderte Dateien mit 146 neuen und 11 gelöschten Zeilen

12
Cargo.lock generiert
Datei anzeigen

@ -319,6 +319,16 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "caps"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b"
dependencies = [
"libc",
"thiserror 1.0.69",
]
[[package]]
name = "cc"
version = "1.2.21"
@ -1518,10 +1528,12 @@ version = "0.1.0"
dependencies = [
"acme2-eab",
"async-scoped",
"caps",
"clap",
"data-encoding",
"env_logger",
"lazy_static",
"libc",
"libsystemd",
"log",
"macro_rules_attribute",

Datei anzeigen

@ -11,6 +11,14 @@ unstable = []
env_logger = "0.11"
lazy_static = "1.5"
toml = "0.8"
[dependencies.caps]
version = "0.5.5"
default-features = false
[dependencies.libc]
version = "0.2"
default-features = false
features = ["const-extern-fn", "std", "extra_traits"]
[dependencies.data-encoding]
version = "2.9"

Datei anzeigen

@ -7,11 +7,15 @@ use lazy_static::lazy_static;
lazy_static! {
pub static ref FILE_MODE: OpenOptions = OpenOptions::new().create(false).read(true).write(false).truncate(false).to_owned();
pub static ref FILE_MODE_WRITE: OpenOptions = OpenOptions::new().create(true).write(true).truncate(true).to_owned();
pub static ref FILE_MODE_WRITE: OpenOptions = OpenOptions::new().create_new(true).write(true).to_owned();
pub static ref LETS_ENCRYPT: String = String::from("letsencrypt");
pub static ref LETS_ENCRYPT_STAGING: String = String::from("letsencrypt-staging");
}
pub fn with_mode_write(mode: u32) -> OpenOptions {
FILE_MODE.clone().mode(mode).to_owned()
}
pub const POOL_SIZE: usize = 1;
pub const MAX_WAIT_TIME: Duration = Duration::from_secs(1 * 60);

Datei anzeigen

@ -65,6 +65,7 @@ use tokio_stream::{
wrappers::ReadDirStream,
};
use types::structs::Error;
use utils::check_permissions;
fn default_client() -> Result<reqwest::Client, Error> {
@ -181,6 +182,12 @@ async fn racme(flags: Arguments) -> Result<(), Error> {
fn main() {
log_init();
if !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)"
);
exit(4)
}
let runtime = match tokio::runtime::Builder::new_current_thread().enable_all().build() {
Ok(runtime) => runtime,
Err(error) => {
@ -192,6 +199,6 @@ fn main() {
runtime.shutdown_timeout(Duration::from_secs(1));
if let Err(error) = result {
error!("{error}");
exit(1)
exit(6)
}
}

Datei anzeigen

@ -4,7 +4,13 @@ use std::{
HashSet,
},
fs::Permissions,
os::unix::fs::PermissionsExt,
os::{
fd::AsFd,
unix::fs::{
PermissionsExt as _,
fchown,
},
},
path::PathBuf,
sync::Arc,
};
@ -13,8 +19,8 @@ use crate::{
consts::{
ATTEMPTS,
FILE_MODE,
FILE_MODE_WRITE,
WAIT_TIME,
with_mode_write,
},
load_privkey,
prelude::*,
@ -31,6 +37,7 @@ use crate::{
},
utils::{
gen_key,
get_uid_gid,
prefix_emails,
},
};
@ -137,8 +144,8 @@ pub async fn accounts(
ac.private_key(key);
}
} else {
info!("Registering for the CA {}", name.clone());
accountkeyfile = match FILE_MODE_WRITE.open(accountkey).await {
info!("creating new key for the account {}", name.clone());
accountkeyfile = match with_mode_write(0o600).open(accountkey).await {
Ok(file) => Some(file),
Err(error) => {
error!("Failed to open the file for the accountkey: {error}");
@ -210,6 +217,7 @@ pub async fn site(args: ProcessorArgs<'_>) {
};
cert_renew = true;
}
let (uid, gid) = get_uid_gid(args.owner(), args.group());
let mut private_key;
// Private key block
{
@ -235,7 +243,11 @@ pub async fn site(args: ProcessorArgs<'_>) {
}
if write_pkey {
let pkey = private_key.private_key_to_pem_pkcs8().unwrap();
let mut file = match_error!(FILE_MODE_WRITE.open(private_key_file.clone()).await=>Err(error)->"Failed to write new private key: {error}");
let mut file = match_error!(with_mode_write(0o640).open(private_key_file.clone()).await=>Err(error)->"Failed to write new private key: {error}");
if let Err(error) = fchown(file.as_fd(), uid, gid) {
error!("Failed to change owner of the new privatekey: {error}");
return;
}
match_error!(file.write_all(&pkey).await=>Err(error)->"Failed to write new private key: {error}");
}
}
@ -323,14 +335,27 @@ pub async fn site(args: ProcessorArgs<'_>) {
Ok(Some(certs)) => certs,
};
debug!("Received {} certificates.", certs.len());
let mut pubkey_file = match_error!(FILE_MODE_WRITE.open(pubkey_filename).await=>Err(error)-> "Failed to open the file for the publickey: {error}");
let mut pubkey_file =
match_error!(with_mode_write(0o644).open(pubkey_filename).await=>Err(error)-> "Failed to open the file for the publickey: {error}");
match_error!(pubkey_file.write_all(&certs[0].to_pem().unwrap()).await=>Err(error)-> "Failed to write the publickey: {error}");
if let Err(error) = fchown(pubkey_file.as_fd(), uid, gid) {
error!("Failed to change owner of the new publickey: {error}");
return;
}
let mut fullchain =
match_error!(FILE_MODE_WRITE.open(directory.join("fullchain.pem")).await=>Err(error)-> "failed to open the fullchain.pem: {error}");
match_error!(with_mode_write(0o644).open(directory.join("fullchain.pem")).await=>Err(error)-> "failed to open the fullchain.pem: {error}");
if let Err(error) = fchown(fullchain.as_fd(), uid, gid) {
error!("Failed to change owner of the new file with the complete chain: {error}");
return;
}
for cert in certs.clone() {
let _ = fullchain.write_all(&cert.to_pem().unwrap()).await;
}
let mut bundle = match_error!(FILE_MODE_WRITE.open(directory.join("bundle.pem")).await=>Err(error)-> "failed to open the bundle.pem: {error}");
let mut bundle = match_error!(with_mode_write(0o640).open(directory.join("bundle.pem")).await=>Err(error)-> "failed to open the bundle.pem: {error}");
if let Err(error) = fchown(bundle.as_fd(), uid, gid) {
error!("Failed to change owner of the new bundle: {error}");
return;
}
let _ = bundle.write_all(&private_key.private_key_to_pem_pkcs8().unwrap()).await;
let _ = bundle.write_all(&certs[0].to_pem().unwrap()).await;
info!("Processing of {} successful", args.name());
@ -360,7 +385,7 @@ pub async fn auth(auth: Authorization, challenge_dir: Option<PathBuf>, dnsserver
match_error!(create_dir_all(directory.clone()).await=>Err(error)-> "Failed to ensure the directory exists: {error}");
let filename = directory.join(challenge.token.clone().unwrap());
let mut challengefile =
match_error!(FILE_MODE_WRITE.open(filename.clone()).await=>Err(error)-> "Failed to open the file for the http-challenge: {error}");
match_error!(with_mode_write(0o644).open(filename.clone()).await=>Err(error)-> "Failed to open the file for the http-challenge: {error}");
match_error!(challengefile.set_permissions(Permissions::from_mode(0o644)).await=>Err(error)-> "Failed to give the file the nessesary permissions: {error}");
match_error!(
challengefile.write_all(challenge.key_authorization().unwrap().unwrap().as_bytes()).await=>Err(error)->

Datei anzeigen

@ -115,6 +115,14 @@ impl CA {
fn default_renew() -> u32 {
7
}
fn default_owner() -> String {
"root".into()
}
fn default_group() -> String {
"root".into()
}
}
#[macro_rules_derive(DefDer)]
@ -156,4 +164,12 @@ pub struct SiteConfig {
#[serde(skip)]
pub name: String,
/// Owner of the Certificate and private key
#[serde(default)]
pub owner: String,
/// Group of the Certificate and private key
#[serde(default)]
pub group: String,
}

Datei anzeigen

@ -72,6 +72,10 @@ impl<'a: 'b, 'b> ProcessorArgs<'a> {
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>,

Datei anzeigen

@ -1,3 +1,5 @@
use std::ffi::CString;
use crate::{
consts::{
BRAINPOOL_MIDDLE,
@ -15,6 +17,10 @@ use crate::{
structs::Error,
},
};
use caps::{
CapSet,
has_cap,
};
use log::*;
use openssl::{
ec::EcKey,
@ -97,3 +103,56 @@ pub fn string_to_cn(name: String) -> X509Name {
builder.append_entry_by_nid(Nid::COMMONNAME, &name).unwrap();
builder.build()
}
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)
}
pub fn get_uid_gid(username: String, group: String) -> (Option<u32>, Option<u32>) {
let uid = if username.contains('\0') {
error!("Invalid Username");
None
} else {
unsafe {
match CString::new(username) {
Ok(cstr) => {
let passwd = libc::getpwnam(cstr.as_ptr());
if !passwd.is_null() {
Some(passwd.read().pw_uid)
} else {
warn!("Failed to get user: User does not exist");
None
}
},
Err(error) => {
error!("Failed to convert the Username to an uid: {error}");
None
},
}
}
};
let gid = if group.contains('\0') {
error!("Invalid Group name");
None
} else {
unsafe {
match CString::new(group) {
Ok(cstr) => {
let passwd = libc::getgrnam(cstr.as_ptr());
if !passwd.is_null() {
Some(passwd.read().gr_gid)
} else {
warn!("Failed to get user: User does not exist");
None
}
},
Err(error) => {
error!("Failed to convert the Group name to an uid: {error}");
None
},
}
}
};
(uid, gid)
}