feat: Fix permission and env errors, add loggings, socket perm args

This commit is contained in:
GW_MC
2025-12-21 18:52:26 +08:00
parent 7781878c2d
commit b823fe6281
3 changed files with 75 additions and 25 deletions

View File

@@ -19,8 +19,10 @@ FROM nginx:mainline-alpine3.23 AS base
# Expose typical HTTP ports used by nginx # Expose typical HTTP ports used by nginx
EXPOSE 80 443 EXPOSE 80 443
ENV S6_KEEP_ENV=1
ENV YANPM_AGENT_SOCK=/var/run/yanpm/yanpm-agent.sock ENV YANPM_AGENT_SOCK=/var/run/yanpm/yanpm-agent.sock
ENV YANPM_NGINX_CONFIG_DIR=/etc/nginx/conf.d ENV YANPM_NGINX_CONFIG_DIR=/etc/nginx/conf.d
ENV YANPM_AGENT_SOCK_PERM=660
WORKDIR /app WORKDIR /app

View File

@@ -1,4 +1,5 @@
#!/bin/sh #!/bin/sh
# Run the agent as the unprivileged 'app' user # Run the agent as the unprivileged 'app' user
cd /app cd /app
echo "Starting yanpm-agent..."
exec s6-setuidgid app ./yanpm-agent exec s6-setuidgid app ./yanpm-agent

View File

@@ -10,20 +10,33 @@ use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use tokio::net::UnixListener; use tokio::net::UnixListener;
use tracing::{error, info}; use tracing::{error, info, warn};
use crate::commands::NginxService; use crate::commands::NginxService;
use crate::routes::{status, validate, validate_and_reload, write_config}; use crate::routes::{status, validate, validate_and_reload, write_config};
const SOCK_ARG: &str = "sock"; const SOCK_ARG: &str = "sock";
const NGINX_CONFIG_DIR_ARG: &str = "nginx_config_dir"; const NGINX_CONFIG_DIR_ARG: &str = "nginx_config_dir";
const SOCK_PERM_ARG: &str = "sock_perm";
const SOCK_ENV: &str = "YANPM_AGENT_SOCK"; const SOCK_ENV: &str = "YANPM_AGENT_SOCK";
const SOCK_PERM_ENV: &str = "YANPM_AGENT_SOCK_PERM";
const NGINX_CONFIG_DIR_ENV: &str = "YANPM_NGINX_CONFIG_DIR"; const NGINX_CONFIG_DIR_ENV: &str = "YANPM_NGINX_CONFIG_DIR";
const SOCK_DEFAULT: &str = "./yanpm-agent.sock"; const SOCK_DEFAULT: &str = "./yanpm-agent.sock";
const NGINX_CONFIG_DIR_DEFAULT: &str = "/etc/nginx/conf.d"; const NGINX_CONFIG_DIR_DEFAULT: &str = "/etc/nginx/conf.d";
const SOCK_PERM_DEFAULT: &str = "660";
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.with_level(true)
.with_timer(tracing_subscriber::fmt::time::SystemTime)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to set global default subscriber");
let args = Command::new("yanpm-agent") let args = Command::new("yanpm-agent")
.arg( .arg(
Arg::new("sock") Arg::new("sock")
@@ -41,30 +54,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.help("Directory where generated nginx config files will be written") .help("Directory where generated nginx config files will be written")
.required(false), .required(false),
) )
.arg(
Arg::new("sock_perm")
.long("sock-perm")
.value_name("SOCK_PERM")
.help("Permissions to set on the unix socket (in octal), e.g. 660")
.required(false),
)
.about("YANPM Agent Daemon") .about("YANPM Agent Daemon")
.get_matches(); .get_matches();
let subscriber = tracing_subscriber::fmt() let (sock, nginx_config_dir, sock_perm) = get_args(&args).await?;
.with_max_level(tracing::Level::INFO)
.with_target(false)
.with_level(true)
.with_timer(tracing_subscriber::fmt::time::SystemTime)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to set global default subscriber");
let sock = args
.get_one::<String>(SOCK_ARG)
.cloned()
.unwrap_or_else(|| std::env::var(SOCK_ENV).unwrap_or_else(|_| SOCK_DEFAULT.to_string()));
let nginx_config_dir = args
.get_one::<String>(NGINX_CONFIG_DIR_ARG)
.cloned()
.unwrap_or_else(|| {
std::env::var(NGINX_CONFIG_DIR_ENV)
.unwrap_or_else(|_| NGINX_CONFIG_DIR_DEFAULT.to_string())
});
let path = PathBuf::from(&sock); let path = PathBuf::from(&sock);
if let Some(dir) = path.parent() { if let Some(dir) = path.parent() {
@@ -112,9 +112,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// bind using tokio's UnixListener (avoids converting a blocking std listener) // bind using tokio's UnixListener (avoids converting a blocking std listener)
let listener = UnixListener::bind(&path).expect("Failed to bind to unix socket"); let listener = UnixListener::bind(&path).expect("Failed to bind to unix socket");
// set socket perms to 0660 (best-effort) // set socket perms to sock_perm (best-effort)
if let Err(err) = if let Err(err) =
tokio::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o660)).await tokio::fs::set_permissions(&path, std::fs::Permissions::from_mode(sock_perm)).await
{ {
error!( error!(
"Warning: failed to set permissions on socket {}: {}", "Warning: failed to set permissions on socket {}: {}",
@@ -132,12 +132,59 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.route("/write_config", post(write_config)) .route("/write_config", post(write_config))
.with_state(NginxService::new(scheduler.clone(), PathBuf::from(nginx_config_dir)).await?); .with_state(NginxService::new(scheduler.clone(), PathBuf::from(nginx_config_dir)).await?);
scheduler.start().await?; scheduler.clone().start().await?;
info!("Starting yanpm-daemon on unix socket: {}", sock); info!("Starting yanpm-daemon on unix socket: {}", sock);
axum::serve::serve(listener, app) axum::serve::serve(listener, app)
.await .await
.expect("Failed to start axum server"); .expect("Failed to start axum server");
info!("Shutting down yanpm-daemon");
Ok(()) Ok(())
} }
async fn get_args(
args: &clap::ArgMatches,
) -> Result<(String, String, u32), Box<dyn std::error::Error + Send + Sync>> {
let sock = args
.get_one::<String>(SOCK_ARG)
.cloned()
.unwrap_or_else(|| std::env::var(SOCK_ENV).unwrap_or_else(|_| SOCK_DEFAULT.to_string()));
let nginx_config_dir = args
.get_one::<String>(NGINX_CONFIG_DIR_ARG)
.cloned()
.unwrap_or_else(|| {
std::env::var(NGINX_CONFIG_DIR_ENV)
.unwrap_or_else(|_| NGINX_CONFIG_DIR_DEFAULT.to_string())
});
let sock_perm = args
.get_one::<String>(SOCK_PERM_ARG)
.cloned()
.unwrap_or_else(|| {
std::env::var(SOCK_PERM_ENV).unwrap_or_else(|_| SOCK_PERM_DEFAULT.to_string())
});
if sock_perm.len() != 3 || !sock_perm.chars().all(|c| ('0'..='7').contains(&c)) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"Invalid socket permission string: {}. Must be a 3-digit octal number.",
sock_perm
),
)
.into());
}
if sock_perm.chars().last().unwrap() > '0' {
warn!(
"Socket permission string {} allows others to access the socket. This may be a security risk.",
sock_perm
);
};
Ok((
sock,
nginx_config_dir,
u32::from_str_radix(&sock_perm, 8).expect("Failed to parse socket permission string"),
))
}