From aeba4cfcaa665d8a0f9142dc59eda4e6b6573a35 Mon Sep 17 00:00:00 2001 From: Dave Parfitt Date: Mon, 27 Jun 2016 17:05:57 -0400 Subject: [PATCH] honor pkg_svc_user/pkg_svc_group Plans that specify pkg_svc_user and pkg_svc_group will have SVC_USER and SVC_GROUP artifact metafiles created during build. The supervisor will attempt to run the child service process as the given user/group. additionally: - if SVC_USER and SVC_GROUP exist, the supervisor will verify the user and group specified within exists. If we are running as the specified user:group, the process will start without changing user:group. If we aren't running as the specified user:group, but are running as root, we exec the child process as SVC_USER:SVC_GROUP. If a pkg_svc_user and pkg_svc_group aren't specified, try running the service as hab:hab if that user:group exists, otherwise, fall back to the current uid:gid. - we no longer symlink to hooks, they are copied to /hab/svc/foo/*. This is so we can chmod the run script as the user/group running the service. - friendlier chmod/chown/mkdir errors that bubble up to the CLI - Tracing added for chmod/chown/mkdir functions - a RuntimeConfig struct is passed into Supervisor that specifies additional params used during child process exec. - Supervisor provides a platform-dependent start_platform() function to allow for future expansion. - hooks are executed as SVC_USER:SVC_GROUP - Platform dependent users module, currently support Linux (osx untested but might just work) Signed-off-by: Dave Parfitt --- components/core/src/error.rs | 6 +- components/core/src/package/install.rs | 20 +++ components/core/src/package/mod.rs | 4 + components/core/src/util/perm.rs | 19 ++- components/depot/Cargo.lock | 1 + components/plan-build/bin/hab-plan-build.sh | 3 + components/sup/Cargo.lock | 1 + components/sup/Cargo.toml | 2 + components/sup/src/error.rs | 3 + components/sup/src/package/hooks.rs | 64 ++++++++- components/sup/src/package/mod.rs | 71 ++++++---- components/sup/src/supervisor.rs | 62 ++++++++- components/sup/src/topology/mod.rs | 16 ++- components/sup/src/util/mod.rs | 1 + components/sup/src/util/users.rs | 142 ++++++++++++++++++++ plans/postgresql/hooks/init | 6 +- plans/postgresql/hooks/run | 2 +- plans/postgresql/plan.sh | 2 + 18 files changed, 371 insertions(+), 54 deletions(-) create mode 100644 components/sup/src/util/users.rs diff --git a/components/core/src/error.rs b/components/core/src/error.rs index 43991987f6..0cf7cd60a1 100644 --- a/components/core/src/error.rs +++ b/components/core/src/error.rs @@ -72,7 +72,7 @@ pub enum Error { /// When an error occurs parsing an integer. ParseIntError(num::ParseIntError), /// Occurs when setting ownership or permissions on a file or directory fails. - PermissionFailed, + PermissionFailed(String), /// When an error occurs parsing or compiling a regular expression. RegexParse(regex::Error), /// When an error occurs converting a `String` from a UTF-8 byte vector. @@ -141,7 +141,7 @@ impl fmt::Display for Error { } } Error::ParseIntError(ref e) => format!("{}", e), - Error::PermissionFailed => format!("Failed to set permissions"), + Error::PermissionFailed(ref e) => format!("{}", e), Error::RegexParse(ref e) => format!("{}", e), Error::StringFromUtf8Error(ref e) => format!("{}", e), Error::UnameFailed(ref e) => format!("{}", e), @@ -187,7 +187,7 @@ impl error::Error for Error { Error::NoOutboundAddr => "Failed to discover the outbound IP address", Error::PackageNotFound(_) => "Cannot find a package", Error::ParseIntError(_) => "Failed to parse an integer from a string!", - Error::PermissionFailed => "Failed to set permissions", + Error::PermissionFailed(_) => "Failed to set permissions", Error::RegexParse(_) => "Failed to parse a regular expression", Error::StringFromUtf8Error(_) => "Failed to convert a string from a Vec as UTF-8", Error::UnameFailed(_) => "uname failed", diff --git a/components/core/src/package/install.rs b/components/core/src/package/install.rs index 23a1921a00..2aafc3fa4f 100644 --- a/components/core/src/package/install.rs +++ b/components/core/src/package/install.rs @@ -223,6 +223,26 @@ impl PackageInstall { fs::svc_var_path(&self.ident.name) } + /// Returns the user that the package is specified to run as + /// or None if the package doesn't contain a SVC_USER Metafile + pub fn svc_user(&self) -> Result> { + match self.read_metafile(MetaFile::SvcUser) { + Ok(body) => Ok(Some(body)), + Err(Error::MetaFileNotFound(MetaFile::SvcUser)) => Ok(None), + Err(e) => Err(e), + } + } + + /// Returns the group that the package is specified to run as + /// or None if the package doesn't contain a SVC_GROUP Metafile + pub fn svc_group(&self) -> Result> { + match self.read_metafile(MetaFile::SvcGroup) { + Ok(body) => Ok(Some(body)), + Err(Error::MetaFileNotFound(MetaFile::SvcGroup)) => Ok(None), + Err(e) => Err(e), + } + } + /// Read the contents of a given metafile. /// /// # Failures diff --git a/components/core/src/package/mod.rs b/components/core/src/package/mod.rs index cc74c2be7f..7d6237399a 100644 --- a/components/core/src/package/mod.rs +++ b/components/core/src/package/mod.rs @@ -34,6 +34,8 @@ pub enum MetaFile { LdFlags, Manifest, Path, + SvcUser, + SvcGroup, } impl fmt::Display for MetaFile { @@ -49,6 +51,8 @@ impl fmt::Display for MetaFile { MetaFile::LdFlags => "LDFLAGS", MetaFile::Manifest => "MANIFEST", MetaFile::Path => "PATH", + MetaFile::SvcUser => "SVC_USER", + MetaFile::SvcGroup => "SVC_GROUP", }; write!(f, "{}", id) } diff --git a/components/core/src/util/perm.rs b/components/core/src/util/perm.rs index a2792551dd..a8089cb877 100644 --- a/components/core/src/util/perm.rs +++ b/components/core/src/util/perm.rs @@ -18,13 +18,20 @@ use std::path::Path; use error::{Error, Result}; pub fn set_owner, X: AsRef>(path: T, owner: X) -> Result<()> { + debug!("Attempting to set owner of {:?} to {:?}", + &path.as_ref(), + &owner.as_ref()); let output = try!(Command::new("chown") .arg(owner.as_ref()) .arg(path.as_ref()) .output()); match output.status.success() { true => Ok(()), - false => Err(Error::PermissionFailed), + false => { + Err(Error::PermissionFailed(format!("Can't change owner of {:?} to {:?}", + &path.as_ref(), + &owner.as_ref()))) + } } } @@ -32,12 +39,20 @@ pub fn set_owner, X: AsRef>(path: T, owner: X) -> Result<()> // platform abstraction. Until then, if we move to Windows or some // other platform, this code will need to become platform specific. pub fn set_permissions, X: AsRef>(path: T, perm: X) -> Result<()> { + debug!("Attempting to set permissions on {:?} to {:?}", + &path.as_ref(), + &perm.as_ref()); let output = try!(Command::new("chmod") .arg(perm.as_ref()) .arg(path.as_ref()) .output()); match output.status.success() { true => Ok(()), - false => Err(Error::PermissionFailed), + false => { + Err(Error::PermissionFailed(format!("Can't set permissions on {:?} to {:?}", + &path.as_ref(), + &perm.as_ref()))) + } } } + diff --git a/components/depot/Cargo.lock b/components/depot/Cargo.lock index 12ac47e52d..20adcdf093 100644 --- a/components/depot/Cargo.lock +++ b/components/depot/Cargo.lock @@ -174,6 +174,7 @@ dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "habitat_builder_protocol 0.7.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2_redis 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/plan-build/bin/hab-plan-build.sh b/components/plan-build/bin/hab-plan-build.sh index 7e8fa65b33..5dd904958f 100755 --- a/components/plan-build/bin/hab-plan-build.sh +++ b/components/plan-build/bin/hab-plan-build.sh @@ -1915,6 +1915,9 @@ _build_metadata() { echo "${pkg_origin}/${pkg_name}/${pkg_version}/${pkg_release}" \ >> $pkg_prefix/IDENT + echo "$pkg_svc_user" > $pkg_prefix/SVC_USER + echo "$pkg_svc_group" > $pkg_prefix/SVC_GROUP + # Generate the blake2b hashes of all the files in the package. This # is not in the resulting MANIFEST because MANIFEST is included! pushd "$HAB_CACHE_SRC_PATH/$pkg_dirname" > /dev/null diff --git a/components/sup/Cargo.lock b/components/sup/Cargo.lock index 3bcf9fa1c5..b25d89ee30 100644 --- a/components/sup/Cargo.lock +++ b/components/sup/Cargo.lock @@ -26,6 +26,7 @@ dependencies = [ "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "urlencoded 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "users 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "wonder 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/components/sup/Cargo.toml b/components/sup/Cargo.toml index 5fb557b64b..5464064064 100644 --- a/components/sup/Cargo.toml +++ b/components/sup/Cargo.toml @@ -36,6 +36,8 @@ openssl = "*" lazy_static = "*" handlebars = "*" wonder = "*" +users = "*" + [dependencies.habitat_core] path = "../core" diff --git a/components/sup/src/error.rs b/components/sup/src/error.rs index adbd64fba5..3e23b10fc7 100644 --- a/components/sup/src/error.rs +++ b/components/sup/src/error.rs @@ -103,6 +103,7 @@ pub enum Error { DepotClient(depot_client::Error), ExecCommandNotFound(String), FileNotFound(String), + Permissions(String), HabitatCommon(common::Error), HabitatCore(hcore::Error), HandlebarsTemplateFileError(handlebars::TemplateFileError), @@ -152,6 +153,7 @@ impl fmt::Display for SupError { Error::ExecCommandNotFound(ref c) => { format!("`{}' was not found on the filesystem or in PATH", c) } + Error::Permissions(ref err) => format!("{}", err), Error::HabitatCommon(ref err) => format!("{}", err), Error::HabitatCore(ref err) => format!("{}", err), Error::HandlebarsTemplateFileError(ref err) => format!("{:?}", err), @@ -239,6 +241,7 @@ impl error::Error for SupError { match self.err { Error::ActorError(_) => "A running actor responded with an error", Error::ExecCommandNotFound(_) => "Exec command was not found on filesystem or in PATH", + Error::Permissions(_) => "File system permissions error", Error::HandlebarsRenderError(ref err) => err.description(), Error::HandlebarsTemplateFileError(ref err) => err.description(), Error::HabitatCommon(ref err) => err.description(), diff --git a/components/sup/src/package/hooks.rs b/components/sup/src/package/hooks.rs index b7340c47af..a6869bd0a6 100644 --- a/components/sup/src/package/hooks.rs +++ b/components/sup/src/package/hooks.rs @@ -22,12 +22,16 @@ use std::process::{Command, Stdio}; use handlebars::Handlebars; use error::{Error, Result}; +use hcore::util; use package::Package; use service_config::{ServiceConfig, never_escape_fn}; use util::convert; +use util::users as hab_users; static LOGKEY: &'static str = "PH"; +pub const HOOK_PERMISSIONS: &'static str = "0755"; + #[derive(Debug, Clone)] pub enum HookType { HealthCheck, @@ -54,24 +58,33 @@ pub struct Hook { pub htype: HookType, pub template: PathBuf, pub path: PathBuf, + pub user: String, + pub group: String, } impl Hook { - pub fn new(htype: HookType, template: PathBuf, path: PathBuf) -> Self { + pub fn new(htype: HookType, + template: PathBuf, + path: PathBuf, + user: String, + group: String) + -> Self { Hook { htype: htype, template: template, path: path, + user: user, + group: group, } } pub fn run(&self, context: Option<&ServiceConfig>) -> Result { try!(self.compile(context)); - let mut child = try!(Command::new(&self.path) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()); + + let mut cmd = Command::new(&self.path); + try!(self.run_platform(&mut cmd)); + let mut child = try!(cmd.spawn()); + { let mut c_stdout = match child.stdout { Some(ref mut s) => s, @@ -113,7 +126,37 @@ impl Hook { } } + #[cfg(any(target_os="linux", target_os="macos"))] + fn run_platform(&self, cmd: &mut Command) -> Result<()> { + use std::os::unix::process::CommandExt; + let uid = hab_users::user_name_to_uid(&self.user); + let gid = hab_users::group_name_to_gid(&self.group); + if let None = uid { + panic!("Can't determine uid"); + } + + if let None = gid { + panic!("Can't determine gid"); + } + + let uid = uid.unwrap(); + let gid = gid.unwrap(); + cmd.stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .uid(uid) + .gid(gid); + Ok(()) + } + + #[cfg(target_os = "windows")] + fn run_platform(&self, cmd: &mut Command) -> Result<()> { + unimplemented!(); + } + pub fn compile(&self, context: Option<&ServiceConfig>) -> Result<()> { + let runas = format!("{}:{}", &self.user, &self.group); + if let Some(ctx) = context { debug!("Rendering hook {:?}", self); let mut handlebars = Handlebars::new(); @@ -130,9 +173,13 @@ impl Hook { .mode(0o770) .open(&self.path)); try!(write!(&mut file, "{}", data)); + try!(util::perm::set_owner(&self.path, &runas)); + try!(util::perm::set_permissions(&self.path, HOOK_PERMISSIONS)); Ok(()) } else { try!(fs::copy(&self.template, &self.path)); + try!(util::perm::set_owner(&self.path, &runas)); + try!(util::perm::set_permissions(&self.path, HOOK_PERMISSIONS)); Ok(()) } } @@ -179,8 +226,11 @@ impl<'a> HookTable<'a> { fn load_hook(&self, hook_type: HookType) -> Option { let template = self.package.hook_template_path(&hook_type); let concrete = self.package.hook_path(&hook_type); + + let (user, group) = hab_users::get_user_and_group(&self.package.pkg_install) + .expect("Can't determine user:group"); match fs::metadata(&template) { - Ok(_) => Some(Hook::new(hook_type, template, concrete)), + Ok(_) => Some(Hook::new(hook_type, template, concrete, user, group)), Err(_) => None, } } diff --git a/components/sup/src/package/mod.rs b/components/sup/src/package/mod.rs index 6461ebf9cb..cd79925b96 100644 --- a/components/sup/src/package/mod.rs +++ b/components/sup/src/package/mod.rs @@ -22,7 +22,6 @@ use std; use std::env; use std::fmt; use std::fs::File; -use std::os::unix; use std::path::{Path, PathBuf}; use std::string::ToString; use std::io::prelude::*; @@ -30,12 +29,13 @@ use std::io::prelude::*; use hcore::package::{PackageIdent, PackageInstall}; use hcore::util; -use self::hooks::HookTable; +use self::hooks::{HookTable, HOOK_PERMISSIONS}; use error::{Error, Result, SupError}; use health_check::{self, CheckResult}; use service_config::ServiceConfig; use supervisor::Supervisor; use util::path::busybox_paths; +use util::users as hab_users; static LOGKEY: &'static str = "PK"; const INIT_FILENAME: &'static str = "init"; @@ -43,8 +43,6 @@ const HEALTHCHECK_FILENAME: &'static str = "health_check"; const FILEUPDATED_FILENAME: &'static str = "file_updated"; const RECONFIGURE_FILENAME: &'static str = "reconfigure"; const RUN_FILENAME: &'static str = "run"; -const SERVICE_PATH_OWNER: &'static str = "hab"; -const SERVICE_PATH_GROUP: &'static str = "hab"; #[derive(Debug, Clone)] pub struct Package { @@ -151,24 +149,51 @@ impl Package { self.pkg_install.svc_path() } + + /// this function wraps create_dir_all so we can give friendly error + /// messages to the user. + fn create_dir_all>(path: P) -> Result<()> { + debug!("Creating dir with subdirs: {:?}", &path.as_ref()); + if let Err(e) = std::fs::create_dir_all(&path) { + Err(sup_error!(Error::Permissions(format!("Can't create {:?}, {}", &path.as_ref(), e)))) + } else { + Ok(()) + } + } + /// Create the service path for this package. pub fn create_svc_path(&self) -> Result<()> { - let runas = format!("{}:{}", SERVICE_PATH_OWNER, SERVICE_PATH_GROUP); + let (user, group) = try!(hab_users::get_user_and_group(&self.pkg_install)); + + let runas = format!("{}:{}", user, group); debug!("Creating svc paths"); - try!(std::fs::create_dir_all(self.pkg_install.svc_config_path())); - try!(std::fs::create_dir_all(self.pkg_install.svc_data_path())); + + + if let Err(e) = Self::create_dir_all(self.pkg_install.svc_path()) { + outputln!("Can't create directory {}", + &self.pkg_install.svc_path().to_str().unwrap()); + outputln!("If this service is running as non-root, you'll need to create \ + {} and give the current user write access to it", + self.pkg_install.svc_path().to_str().unwrap()); + return Err(e); + } + + try!(Self::create_dir_all(self.pkg_install.svc_config_path())); + try!(util::perm::set_owner(self.pkg_install.svc_config_path(), &runas)); + try!(util::perm::set_permissions(self.pkg_install.svc_config_path(), "0700")); + try!(Self::create_dir_all(self.pkg_install.svc_data_path())); try!(util::perm::set_owner(self.pkg_install.svc_data_path(), &runas)); try!(util::perm::set_permissions(self.pkg_install.svc_data_path(), "0700")); - try!(std::fs::create_dir_all(self.pkg_install.svc_files_path())); + try!(Self::create_dir_all(self.pkg_install.svc_files_path())); try!(util::perm::set_owner(self.pkg_install.svc_files_path(), &runas)); try!(util::perm::set_permissions(self.pkg_install.svc_files_path(), "0700")); - try!(std::fs::create_dir_all(self.pkg_install.svc_hooks_path())); - try!(std::fs::create_dir_all(self.pkg_install.svc_var_path())); + try!(Self::create_dir_all(self.pkg_install.svc_hooks_path())); + try!(Self::create_dir_all(self.pkg_install.svc_var_path())); try!(util::perm::set_owner(self.pkg_install.svc_var_path(), &runas)); try!(util::perm::set_permissions(self.pkg_install.svc_var_path(), "0700")); // TODO: Not 100% if this directory is still needed, but for the moment it's still here - // FIN - try!(std::fs::create_dir_all(self.pkg_install.svc_path().join("toml"))); + try!(Self::create_dir_all(self.pkg_install.svc_path().join("toml"))); try!(util::perm::set_permissions(self.pkg_install.svc_path().join("toml"), "0700")); Ok(()) } @@ -177,28 +202,20 @@ impl Package { pub fn copy_run(&self, context: &ServiceConfig) -> Result<()> { debug!("Copying the run file"); let svc_run = self.pkg_install.svc_path().join(RUN_FILENAME); + debug!("svc_run = {}", &svc_run.to_str().unwrap()); if let Some(hook) = self.hooks().run_hook { + debug!("Comiling hook"); try!(hook.compile(Some(context))); - match std::fs::read_link(&svc_run) { - Ok(path) => { - if path != hook.path { - try!(util::perm::set_permissions(hook.path.to_str().unwrap(), "0755")); - try!(std::fs::remove_file(&svc_run)); - try!(unix::fs::symlink(hook.path, &svc_run)); - } - } - Err(_) => try!(unix::fs::symlink(hook.path, &svc_run)), - } + try!(std::fs::copy(hook.path, &svc_run)); + try!(util::perm::set_permissions(&svc_run.to_str().unwrap(), HOOK_PERMISSIONS)); } else { let run = self.path().join(RUN_FILENAME); match std::fs::metadata(&run) { Ok(_) => { - try!(util::perm::set_permissions(&run, "0755")); - match std::fs::metadata(&svc_run) { - Ok(_) => try!(std::fs::remove_file(&svc_run)), - Err(_) => {} - } - try!(unix::fs::symlink(&run, &svc_run)); + debug!("run file = {}", &run.to_str().unwrap()); + debug!("svc_run file = {}", &svc_run.to_str().unwrap()); + try!(std::fs::copy(&run, &svc_run)); + try!(util::perm::set_permissions(&svc_run, HOOK_PERMISSIONS)); } Err(e) => { outputln!("Error finding the run file: {}", e); diff --git a/components/sup/src/supervisor.rs b/components/sup/src/supervisor.rs index a19d2180b4..cc5415ff81 100644 --- a/components/sup/src/supervisor.rs +++ b/components/sup/src/supervisor.rs @@ -33,6 +33,7 @@ use time::{Duration, SteadyTime}; use error::{Result, Error}; use util::signals; +use util::users as hab_users; const PIDFILE_NAME: &'static str = "PID"; static LOGKEY: &'static str = "SV"; @@ -118,6 +119,26 @@ impl fmt::Display for ProcessState { } } + +/// Additional params used to start the Supervisor. +/// These params are outside the scope of what is in +/// Supervisor.package_ident, and aren't runtime params that are stored +/// in the top-level Supervisor struct (such as PID etc) +#[derive(Debug)] +pub struct RuntimeConfig { + pub svc_user: String, + pub svc_group: String, +} + +impl RuntimeConfig { + pub fn new(svc_user: String, svc_group: String) -> RuntimeConfig { + RuntimeConfig { + svc_user: svc_user, + svc_group: svc_group, + } + } +} + #[derive(Debug)] pub struct Supervisor { pub pid: Option, @@ -125,16 +146,18 @@ pub struct Supervisor { pub state: ProcessState, pub state_entered: SteadyTime, pub has_started: bool, + pub runtime_config: RuntimeConfig, } impl Supervisor { - pub fn new(package_ident: PackageIdent) -> Supervisor { + pub fn new(package_ident: PackageIdent, runtime_config: RuntimeConfig) -> Supervisor { Supervisor { pid: None, package_ident: package_ident, state: ProcessState::Down, state_entered: SteadyTime::now(), has_started: false, + runtime_config: runtime_config, } } @@ -159,11 +182,10 @@ impl Supervisor { if self.pid.is_none() { outputln!(preamble & self.package_ident.name, "Starting"); self.enter_state(ProcessState::Start); - let mut child = try!(Command::new(self.run_cmd()) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()); + + let mut cmd = Command::new(self.run_cmd()); + try!(self.start_platform(&mut cmd)); + let mut child = try!(cmd.spawn()); self.pid = Some(child.id()); try!(self.create_pidfile()); let package_name = self.package_ident.name.clone(); @@ -178,6 +200,34 @@ impl Supervisor { Ok(()) } + #[cfg(any(target_os="linux", target_os="macos"))] + fn start_platform(&mut self, cmd: &mut Command) -> Result<()> { + use std::os::unix::process::CommandExt; + let uid = hab_users::user_name_to_uid(&self.runtime_config.svc_user); + let gid = hab_users::group_name_to_gid(&self.runtime_config.svc_group); + if let None = uid { + panic!("Can't determine uid"); + } + + if let None = gid { + panic!("Can't determine gid"); + } + + let uid = uid.unwrap(); + let gid = gid.unwrap(); + cmd.stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .uid(uid) + .gid(gid); + Ok(()) + } + + #[cfg(target_os = "windows")] + fn start_platform(&mut self, cmd: &mut Command) -> Result<()> { + unimplemented!(); + } + /// Send a SIGTERM to a process, wait 8 seconds, then send SIGKILL pub fn stop(&mut self) -> Result<()> { let wait = match self.pid { diff --git a/components/sup/src/topology/mod.rs b/components/sup/src/topology/mod.rs index e7c5a94b88..f63419911c 100644 --- a/components/sup/src/topology/mod.rs +++ b/components/sup/src/topology/mod.rs @@ -26,6 +26,7 @@ pub mod standalone; pub mod leader; pub mod initializer; + use std::mem; use std::net::SocketAddrV4; use std::ops::DerefMut; @@ -46,13 +47,14 @@ use error::{Result, SupError}; use config::Config; use service_config::ServiceConfig; use sidecar; -use supervisor::Supervisor; +use supervisor::{RuntimeConfig, Supervisor}; use gossip; use gossip::rumor::{Rumor, RumorList}; use gossip::member::MemberList; use election::ElectionList; use time::SteadyTime; use util::signals; +use util::users as hab_users; use config::UpdateStrategy; static LOGKEY: &'static str = "TP"; @@ -129,15 +131,21 @@ impl<'a> Worker<'a> { pub fn new(package: Package, topology: String, config: &'a Config) -> Result> { let mut pkg_updater = None; let package_name = package.name.clone(); + + let (svc_user, svc_group) = try!(hab_users::get_user_and_group(&package.pkg_install)); + outputln!("Child process will run as user={}, group={}", + &svc_user, + &svc_group); + let runtime_config = RuntimeConfig::new(svc_user, svc_group); + let package_exposes = package.exposes().clone(); let package_port = package_exposes.first().map(|e| e.clone()); let package_ident = package.ident().clone(); let pkg_lock = Arc::new(RwLock::new(package)); let pkg_lock_1 = pkg_lock.clone(); - match config.update_strategy() { - UpdateStrategy::None => {}, + UpdateStrategy::None => {} _ => { let pkg_lock_2 = pkg_lock.clone(); if let &Some(ref url) = config.url() { @@ -173,7 +181,7 @@ impl<'a> Worker<'a> { let service_config_lock = Arc::new(RwLock::new(service_config)); let service_config_lock_1 = service_config_lock.clone(); - let supervisor = Arc::new(RwLock::new(Supervisor::new(package_ident))); + let supervisor = Arc::new(RwLock::new(Supervisor::new(package_ident, runtime_config))); let sidecar_ml = gossip_server.member_list.clone(); let sidecar_rl = gossip_server.rumor_list.clone(); diff --git a/components/sup/src/util/mod.rs b/components/sup/src/util/mod.rs index 13b824d508..b4deaae55e 100644 --- a/components/sup/src/util/mod.rs +++ b/components/sup/src/util/mod.rs @@ -16,6 +16,7 @@ pub mod convert; pub mod path; pub mod sys; pub mod signals; +pub mod users; use std::net::Ipv4Addr; use std::net::SocketAddrV4; diff --git a/components/sup/src/util/users.rs b/components/sup/src/util/users.rs new file mode 100644 index 0000000000..99d36b6390 --- /dev/null +++ b/components/sup/src/util/users.rs @@ -0,0 +1,142 @@ +// Copyright (c) 2016 Chef Software Inc. and/or applicable contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(any(target_os="linux", target_os="macos"))] +extern crate users; + +use error::{Result, Error}; +use hcore::package::PackageInstall; + +static LOGKEY: &'static str = "USERS"; + +const DEFAULT_USER: &'static str = "hab"; +const DEFAULT_GROUP: &'static str = "hab"; + +/// This function checks to see if a custom SVC_USER and SVC_GROUP has +/// been specified as part of the package metadata. +/// If a pkg_svc_user and pkg_svc_group have been defined, check if: +/// a) we are root +/// b) we are the specified user:group +/// c) fail otherwise +/// If pkg_svc_user and pkg_svc_group have NOT been defined, return None. +#[cfg(any(target_os="linux", target_os="macos"))] +fn check_pkg_user_and_group(pkg_install: &PackageInstall) -> Result> { + let svc_user = try!(pkg_install.svc_user()); + let svc_group = try!(pkg_install.svc_group()); + match (svc_user, svc_group) { + (Some(user), Some(group)) => { + // a package has a SVC_USER and SVC_GROUP defined, + // these MUST exist in order to continue + debug!("SVC_USER = {}", &user); + debug!("SVC_GROUP = {}", &group); + if let None = users::get_user_by_name(&user) { + return Err(sup_error!(Error::Permissions(format!("Package requires user {} to \ + exist, but it doesn't", + user)))); + } + if let None = users::get_group_by_name(&group) { + return Err(sup_error!(Error::Permissions(format!("Package requires group {} \ + to exist, but it doesn't", + group)))); + } + + let current_user = users::get_current_username(); + let current_group = users::get_current_groupname(); + + if let None = current_user { + return Err(sup_error!(Error::Permissions("Can't determine current user" + .to_string()))); + } + + if let None = current_group { + return Err(sup_error!(Error::Permissions("Can't determine current group" + .to_string()))); + } + + let current_user = current_user.unwrap(); + let current_group = current_group.unwrap(); + + if current_user == "root" { + Ok(Some((user, group))) + } else { + if current_user == user && current_group == group { + // ok, sup is running as svc_user/svc_group already + Ok(Some((user, group))) + } else { + let msg = format!("Package must run as {}:{} or root", &user, &group); + return Err(sup_error!(Error::Permissions(msg))); + } + } + } + _ => { + debug!("User/group not specified in package, running with default"); + Ok(None) + } + } +} + +/// checks to see if hab/hab exists, if not, fall back to +/// current user/group. If that fails, then return an error. +#[cfg(any(target_os="linux", target_os="macos"))] +fn get_default_user_and_group() -> Result<(String, String)> { + let user = users::get_user_by_name(DEFAULT_USER); + let group = users::get_group_by_name(DEFAULT_GROUP); + match (user, group) { + (Some(user), Some(group)) => return Ok((user.name().to_string(), group.name().to_string())), + _ => { + debug!("hab:hab does NOT exist"); + let user = users::get_current_username(); + let group = users::get_current_groupname(); + match (user, group) { + (Some(user), Some(group)) => { + debug!("Running as {}/{}", user, group); + return Ok((user, group)); + } + _ => { + return Err(sup_error!(Error::Permissions("Can't determine current user:group" + .to_string()))) + } + } + } + } +} + +/// check and see if a user/group is specified in package metadata. +/// if not, we'll try and use hab/hab. +/// If hab/hab doesn't exist, try to use (current username, current group). +/// If that doesn't work, then give up. +#[cfg(any(target_os="linux", target_os="macos"))] +pub fn get_user_and_group(pkg_install: &PackageInstall) -> Result<(String, String)> { + if let Some((user, group)) = try!(check_pkg_user_and_group(&pkg_install)) { + Ok((user, group)) + } else { + let defaults = try!(get_default_user_and_group()); + Ok(defaults) + } +} + +#[cfg(any(target_os="linux", target_os="macos"))] +pub fn user_name_to_uid(user: &str) -> Option { + users::get_user_by_name(user).map(|u| u.uid()) +} + +#[cfg(any(target_os="linux", target_os="macos"))] +pub fn group_name_to_gid(group: &str) -> Option { + users::get_group_by_name(group).map(|g| g.gid()) +} + +#[cfg(target_os = "windows")] +pub fn get_user_and_group(pkg_install: &PackageInstall) -> Result<(String, String)> { + unimplemented!(); +} diff --git a/plans/postgresql/hooks/init b/plans/postgresql/hooks/init index f8a545c22e..ccfda56449 100644 --- a/plans/postgresql/hooks/init +++ b/plans/postgresql/hooks/init @@ -6,13 +6,11 @@ exec 2>&1 mkdir -p {{pkg.svc_config_path}}/conf.d mkdir -p {{pkg.svc_var_path}}/pg_stat_tmp -chown -R hab:hab {{pkg.svc_var_path}} if [[ ! -f "{{pkg.svc_data_path}}/PG_VERSION" ]]; then echo " Database does not exist, creating with 'initdb'" - chpst -u hab:hab initdb -U {{cfg.initdb_superuser_name}} \ + initdb -U {{cfg.initdb_superuser_name}} \ -E {{cfg.initdb_encoding}} \ -D {{pkg.svc_data_path}} \ - --pwfile {{cfg.initdb_pwfile}} && \ - rm {{pkg.svc_config_path}}/pwfile + --pwfile {{cfg.initdb_pwfile}} fi diff --git a/plans/postgresql/hooks/run b/plans/postgresql/hooks/run index 0d462d8ad8..48e6f40aa3 100644 --- a/plans/postgresql/hooks/run +++ b/plans/postgresql/hooks/run @@ -4,5 +4,5 @@ export PGDATA={{pkg.svc_data_path}} exec 2>&1 -exec chpst -u hab:hab {{pkg.path}}/bin/postmaster \ +exec {{pkg.path}}/bin/postmaster \ -c config_file={{pkg.svc_path}}/config/postgresql.conf diff --git a/plans/postgresql/plan.sh b/plans/postgresql/plan.sh index 9b2151a77b..aa9844471f 100644 --- a/plans/postgresql/plan.sh +++ b/plans/postgresql/plan.sh @@ -5,6 +5,8 @@ pkg_maintainer="The Habitat Maintainers " pkg_license=('PostgreSQL') pkg_source=https://ftp.postgresql.org/pub/source/v${pkg_version}/${pkg_name}-${pkg_version}.tar.bz2 pkg_shasum=7385c01dc58acba8d7ac4e6ad42782bd7c0b59272862a3a3d5fe378d4503a0b4 +pkg_svc_user=hab +pkg_svc_group=hab pkg_deps=( core/glibc