racme can now change the ownership of files
Dieser Commit ist enthalten in:
Ursprung
01a4a64ae6
Commit
b8fca09327
8 geänderte Dateien mit 146 neuen und 11 gelöschten Zeilen
12
Cargo.lock
generiert
12
Cargo.lock
generiert
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)->
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
59
src/utils.rs
59
src/utils.rs
|
@ -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)
|
||||
}
|
||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren