From f4a38de0c07d6aead324918325064a3dd3b19a3d Mon Sep 17 00:00:00 2001 From: Master Date: Mon, 29 Jun 2020 02:22:11 -0400 Subject: [PATCH] added group and user set[uid|gid] capability at unit definition level --- examplar.cpp | 10 ++- src/Sproc/Sproc.cpp | 107 ++++++++++++++++++++++-- src/Sproc/Sproc.h | 4 +- src/loaders/abstract/Task.cpp | 18 ++-- src/loaders/abstract/Unit.cpp | 62 ++++++++++++++ src/loaders/abstract/Unit.h | 10 +++ test/components/dependent_test.bash | 2 + test/components/independent_test_1.bash | 11 +++ test/components/independent_test_2.bash | 2 + test/config.json | 4 +- test/plans/atomic.plan | 5 ++ test/units/all_test.units | 34 +++----- test/units/dependent_tests.units | 10 ++- 13 files changed, 236 insertions(+), 43 deletions(-) create mode 100755 test/components/dependent_test.bash create mode 100755 test/components/independent_test_1.bash create mode 100755 test/components/independent_test_2.bash create mode 100644 test/plans/atomic.plan diff --git a/examplar.cpp b/examplar.cpp index 7b477c6..c6f8da5 100644 --- a/examplar.cpp +++ b/examplar.cpp @@ -137,15 +137,22 @@ int main( int argc, char * argv[] ) std::string definitions_file = configuration.get_units_path(); std::string plan_file = configuration.get_plan_path(); + slog.log( E_DEBUG, "* Initialising suite (definition library)."); Suite available_definitions = Suite( L_LEVEL ); + + slog.log( E_INFO, "* Loading all actionable units into suite." ); available_definitions.load_units_file( definitions_file ); + slog.log( E_DEBUG, "* Initialising plan." ); Plan plan = Plan( &configuration, L_LEVEL ); + + slog.log( E_INFO, "* Loading plan outline."); plan.load_plan_file( plan_file ); + slog.log( E_INFO, "* Loading planned tasks from suite to plan." ); plan.load_definitions( available_definitions ); - slog.log( E_DEBUG, "Ready to execute all tasks in Plan." ); + slog.log( E_INFO, "* Ready to execute all actionable tasks in plan." ); try { @@ -154,6 +161,7 @@ int main( int argc, char * argv[] ) catch ( std::exception& e) { + slog.log( E_FATAL, "Caught exception."); slog.log( E_FATAL, e.what() ); return 1; } diff --git a/src/Sproc/Sproc.cpp b/src/Sproc/Sproc.cpp index c2bc0c6..99298b0 100644 --- a/src/Sproc/Sproc.cpp +++ b/src/Sproc/Sproc.cpp @@ -1,15 +1,108 @@ #include "Sproc.h" #include -#include -#include +#include +#include +#include +#include "../Logger/Logger.h" + +int username_to_uid( std::string username, int & uid ) +{ + // assume failure unless proven otherwise + int r_code = false; + + struct passwd * pw; + if ( ( pw = getpwnam( username.c_str() ) ) != NULL ) + { + // successful user lookup + r_code = true; + uid = pw->pw_uid; + } else { + // failed lookup, do nothing, assumed failure + } + return r_code; +}; + +int groupname_to_gid( std::string groupname, int & gid ) +{ + int r_code = false; + + struct group * gp; + if ( ( gp = getgrnam( groupname.c_str() ) ) != NULL ) + { + r_code = true; + gid = gp->gr_gid; + } else { + // failed lookup, do nothing, assumed failure + } + return r_code; +} /// Sproc::execute /// /// \param input - The commandline input to execute. /// \return - The return code of the execution of input in the calling shell. -int Sproc::execute(std::string input) { - int child_exit_code = -666; - child_exit_code = system( input.c_str() ); - child_exit_code = WEXITSTATUS( child_exit_code ); - return child_exit_code; +int Sproc::execute(std::string run_as, std::string group, std::string command ) +{ + Logger slog = Logger( E_INFO, "_sproc" ); + + // the run_as_uid to capture the run_as_uid to run as + int run_as_uid; + int run_as_gid; + + slog.log( E_DEBUG, "Attempt: Running as user '" + run_as + "'."); + slog.log( E_DEBUG, "Attempt: Running as group '" + group + "'."); + + if ( username_to_uid( run_as, run_as_uid ) ) + { + slog.log( E_DEBUG, "UID of '" + run_as + "' is '" + std::to_string( run_as_uid ) + "'." ); + } else { + slog.log( E_FATAL, "Failed to look up UID for '" + run_as + "'."); + return -404; + } + + if ( groupname_to_gid( group, run_as_gid ) ) + { + slog.log( E_DEBUG, "GID of '" + group + "' is '" + std::to_string( run_as_gid ) + "'." ); + } else { + slog.log( E_FATAL, "Failed to look up DID for '" + group + "'."); + return -404; + } + + // if you get this return value, it's an issue with this method and not your + // called executable. + int exit_code_raw = -666; + + // fork a process + int pid = fork(); + + if ( pid == 0 ) + { + // child process + if ( seteuid( run_as_uid ) == 0 ) + { + slog.log( E_DEBUG, "Successfully set UID to '" + std::to_string(run_as_uid) + "'." ); + } else { + slog.log( E_FATAL, "Failed to set UID. Panicking." ); + return -401; + } + if ( setegid( run_as_gid ) == 0 ) + { + slog.log( E_DEBUG, "Successfully set GID to '" + std::to_string(run_as_gid) + "'." ); + } else { + slog.log( E_FATAL, "Failed to set GID. Panicking." ); + return -401; + } + exit_code_raw = system( command.c_str() ); + exit( WEXITSTATUS( exit_code_raw ) ); + } else if ( pid > 0 ) + { + // parent process + while ( ( pid = waitpid( pid, &exit_code_raw, 0 ) ) == -1 ) {} + } else { + // fork failed + slog.log( E_FATAL, "Fork Failed"); + } + return WEXITSTATUS( exit_code_raw ); + + } \ No newline at end of file diff --git a/src/Sproc/Sproc.h b/src/Sproc/Sproc.h index 94b2481..3eac63c 100644 --- a/src/Sproc/Sproc.h +++ b/src/Sproc/Sproc.h @@ -23,13 +23,15 @@ #include #include +#include +#include "../Logger/Logger.h" // executes a subprocess and captures STDOUT, STDERR, and return code. // should be able to recieve path of binary to be executed as well as any parameters class Sproc { public: // call the object. returnvalue is enum representing external execution attempt not binary exit code - static int execute( std::string input ); + static int execute(std::string run_as, std::string group, std::string command ); }; #endif //FTESTS_SPROC_H diff --git a/src/loaders/abstract/Task.cpp b/src/loaders/abstract/Task.cpp index 0d978df..e5f9841 100644 --- a/src/loaders/abstract/Task.cpp +++ b/src/loaders/abstract/Task.cpp @@ -132,7 +132,7 @@ std::string Task::get_name() void Task::load_definition( Unit selected_unit ) { this->definition = selected_unit; - this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" for task \"" + this->get_name() + "\"."); + this->slog.log( E_INFO, "Loaded definition \"" + selected_unit.get_name() + "\" as task in configured plan."); this->defined = true; } @@ -184,7 +184,7 @@ void Task::execute( Conf * configuration ) // END PREWORK // get the target execution command - std::string target_command = configuration->get_execution_context() + + "/" + this->definition.get_target(); + std::string target_command = this->definition.get_target(); // check if context override if ( configuration->has_context_override() ) @@ -197,10 +197,18 @@ void Task::execute( Conf * configuration ) // a[0] execute target // TODO revise variable sourcing strategy + // ....sourcing on the shell for variables and environment population doesn't have a good smell. this->slog.log( E_INFO, "Executing target: \"" + target_command + "\"." ); + if ( exists( target_command ) ) + { + this->slog.log( E_DEBUG, "Executable exists."); + } else { + this->slog.log( E_FATAL, "Executable does not exist." ); + throw Task_NotReady(); + } this->slog.log( E_DEBUG, "Vars file: " + configuration->get_env_vars_file() ); - int return_code = Sproc::execute( ". " + configuration->get_env_vars_file() + " && " + target_command ); + int return_code = Sproc::execute( this->definition.get_user(), this->definition.get_group(), ". " + configuration->get_env_vars_file() + " && " + target_command ); // ********************************************** // d[0] Error Code Check @@ -259,7 +267,7 @@ void Task::execute( Conf * configuration ) this->slog.log( E_INFO, "Executing rectification: " + rectifier_command + "." ); - int rectifier_error = Sproc::execute( "source " + configuration->get_env_vars_file() + " && " + rectifier_command ); + int rectifier_error = Sproc::execute( this->definition.get_user(), this->definition.get_group(), ". " + configuration->get_env_vars_file() + " && " + rectifier_command ); // ********************************************** // d[3] Error Code Check for Rectifier @@ -300,7 +308,7 @@ void Task::execute( Conf * configuration ) // a[7] Re-execute Target this->slog.log( E_INFO, "Re-Executing target \"" + this->definition.get_target() + "\"." ); - int retry_code = Sproc::execute( "source " + configuration->get_env_vars_file() + " && " + target_command ); + int retry_code = Sproc::execute( this->definition.get_user(), this->definition.get_group(), ". " + configuration->get_env_vars_file() + " && " + target_command ); // ********************************************** // d[5] Error Code Check diff --git a/src/loaders/abstract/Unit.cpp b/src/loaders/abstract/Unit.cpp index 88fcfa2..cba9540 100644 --- a/src/loaders/abstract/Unit.cpp +++ b/src/loaders/abstract/Unit.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include /// Unit_NotPopulated - Meant to be thrown when a Unit type is not populated before being used. /// Signaled by use of the 'populated' boolean member of the Unit class. @@ -30,6 +32,11 @@ class Unit_NotPopulated: public std::runtime_error { public: Unit_NotPopulated(): std::runtime_error("Unit: Attempted to access a member before loading values.") {} }; +/// EnvironmentErrorFatal - Meant to be thrown when the environment is too broken for Examplar to do its job. +class EnvironmentErrorFatal: public std::runtime_error { public: + EnvironmentErrorFatal(): std::runtime_error("Unit: Environment is too broken to continue.") {} +}; + /// Unit_DataStructureException - Meant to be thrown when a Unit type is accessing a member that does not exist. class Unit_DataStructureException: public std::runtime_error { public: // TODO rework this to accept the key name being fetched @@ -83,6 +90,44 @@ int Unit::load_root(Json::Value loader_root) if ( loader_root.isMember("rectify") ) { this->rectify = loader_root.get("rectify", errmsg).asBool(); } else throw Unit_DataStructureException(); + // TODO functionize this + char * lgn; + std::string errmsg_user; + + // if no user field is specified then default to the currently executing user + if ( ( lgn = getlogin() ) == NULL ) + { + throw EnvironmentErrorFatal(); + } else { + errmsg_user = lgn; + } + // -TODO + + + + if ( loader_root.isMember( "user" ) ) + { this->user = loader_root.get( "user", errmsg_user ).asString(); } else this->user = lgn; + + + // TODO functionalize this + // get the current context gid as a backup value + int gid = getgid(); + // declare the grp object to pull the name from once populated + struct group * grp; + // storage for backup value once retrieved + std::string errmsg_group; + + // get the backup value and store it to errmsg_group + if ( ( grp = getgrgid( gid ) ) == NULL ) + { + throw EnvironmentErrorFatal(); + } else { + errmsg_group = grp->gr_name; + } + + if ( loader_root.isMember( "group" ) ) + { this->group = loader_root.get( "group", errmsg_group ).asString(); } else this->group = grp->gr_name; + this->populated = true; return 0; @@ -164,4 +209,21 @@ bool Unit::get_rectify() { if ( ! this->populated ) { throw Unit_NotPopulated(); } return this->rectify; +} + +/// Unit::get_user - retrieves the user context for the unit. +/// +/// \return the string value of the user name. +std::string Unit::get_user() +{ + if ( ! this->populated ) { throw Unit_NotPopulated(); } + return this->user; +} +/// Unit::get_group - retrieves the group context for the unit. +/// +/// \return the string value of the group name. +std::string Unit::get_group() +{ + if ( ! this->populated ) { throw Unit_NotPopulated(); } + return this->group; } \ No newline at end of file diff --git a/src/loaders/abstract/Unit.h b/src/loaders/abstract/Unit.h index 6a48e62..9261fad 100644 --- a/src/loaders/abstract/Unit.h +++ b/src/loaders/abstract/Unit.h @@ -59,6 +59,14 @@ private: // if rectifier exits on non-zero return code, it should be trigger the behaviour indicated by required bool rectify; + // user to run process as. + // not intended for protected accounts, handle your own security + std::string user; + + // group to run process as. + // not intended for protected accounts, handle your own security + std::string group; + public: Unit( int LOG_LEVEL ); @@ -76,6 +84,8 @@ public: bool get_active(); bool get_required(); bool get_rectify(); + std::string get_user(); + std::string get_group(); private: int LOG_LEVEL; diff --git a/test/components/dependent_test.bash b/test/components/dependent_test.bash new file mode 100755 index 0000000..e04aa40 --- /dev/null +++ b/test/components/dependent_test.bash @@ -0,0 +1,2 @@ +echo "dependent test" +exit $? \ No newline at end of file diff --git a/test/components/independent_test_1.bash b/test/components/independent_test_1.bash new file mode 100755 index 0000000..95c660b --- /dev/null +++ b/test/components/independent_test_1.bash @@ -0,0 +1,11 @@ +whoami +id + +touch /home/bagira/testfile + +stat /home/bagira/testfile + +#dialog --stdout --title "Interact with me!" \ +# --backtitle "This is user interaction." \ +# --yesno "Yes: pass, No: fail" 7 60 +exit $? \ No newline at end of file diff --git a/test/components/independent_test_2.bash b/test/components/independent_test_2.bash new file mode 100755 index 0000000..1f2a778 --- /dev/null +++ b/test/components/independent_test_2.bash @@ -0,0 +1,2 @@ +echo "independent test 2 output" +exit $? diff --git a/test/config.json b/test/config.json index 37909dc..c53658a 100644 --- a/test/config.json +++ b/test/config.json @@ -1,8 +1,8 @@ { "execution_context_override": true, "execution_context": "/home/bagira/development/internal/examplar/test", - "units_path": "units/all_test.units", - "plan_path": "plans/test.plan", + "units_path": "units/", + "plan_path": "plans/atomic.plan", "config_version": "3", "env_vars_file": "examplar.variables" } diff --git a/test/plans/atomic.plan b/test/plans/atomic.plan new file mode 100644 index 0000000..951198e --- /dev/null +++ b/test/plans/atomic.plan @@ -0,0 +1,5 @@ +{ + "plan": [ + { "name": "independent test 1", "dependencies": [ null ] } + ] +} diff --git a/test/units/all_test.units b/test/units/all_test.units index 89f167e..d6ccd4d 100644 --- a/test/units/all_test.units +++ b/test/units/all_test.units @@ -2,35 +2,23 @@ "units": [ { "name": "independent test 1", - "target": "/usr/bin/true", - "rectifier": "/usr/bin/true", + "target": "components/independent_test_1.bash", + "rectifier": "", "active": true, "required": true, - "rectify": true - }, - { - "name": "independent test 2", - "target": "/usr/bin/true", - "rectifier": "/usr/bin/true", - "active": true, - "required": false, + "user": "root", + "group": "root", "rectify": false }, { - "name": "A DEFINITION THAT IS NOT USED", - "target": "/usr/bin/dialog --yesno test 50 50", - "rectifier": "/usr/bin/false", - "active": false, - "required": false, - "rectify": true - }, - { - "name": "dependent test", - "target": "/usr/bin/false", - "rectifier": "/usr/bin/true", + "name": "independent test 2", + "target": "components/independent_test_2.bash", + "rectifier": "", "active": true, - "required": true, - "rectify": true + "required": false, + "user": "bagira", + "group": "bagira", + "rectify": false } ] } diff --git a/test/units/dependent_tests.units b/test/units/dependent_tests.units index c39864d..8543b47 100644 --- a/test/units/dependent_tests.units +++ b/test/units/dependent_tests.units @@ -4,17 +4,19 @@ "name": "A DEFINITION THAT IS NOT USED", "target": "/usr/bin/dialog --yesno test 50 50", "rectifier": "/usr/bin/false", - "active": true, + "active": false, "required": true, - "rectify": true + "rectify": true, + "user": "root" }, { "name": "dependent test", "target": "/usr/bin/false", "rectifier": "/usr/bin/true", - "active": true, + "active": false, "required": true, - "rectify": true + "rectify": true, + "user": "root" } ] }