From a7fb94589cdabaabe61b4e8f9f6655bce0a02504 Mon Sep 17 00:00:00 2001 From: Chris Freehill Date: Fri, 20 Mar 2020 10:32:25 -0500 Subject: [PATCH] Handle different levels of rdcd privilege Depending on how a user starts rdcd, rdcd will either have full monitor/control capabilities or have just monitoring capabilties. The only 2 user ids allowed are "rdc" and root. Change-Id: Ie296a2f68c9723bef5945b1af1070ef99eeea93b [ROCm/rdc commit: a6acf24ae74a2ede0892f29592b64f26373c3aab] --- projects/rdc/authentication/install_client.sh | 2 + projects/rdc/authentication/install_server.sh | 3 + projects/rdc/docs/README.md | 61 +++++- projects/rdc/include/rdc/rdc.h | 6 +- projects/rdc/server/src/rdc_server_main.cc | 204 +++++++++++++++--- 5 files changed, 235 insertions(+), 41 deletions(-) mode change 100644 => 100755 projects/rdc/include/rdc/rdc.h diff --git a/projects/rdc/authentication/install_client.sh b/projects/rdc/authentication/install_client.sh index 7c3163ea6f..9b89171ce0 100755 --- a/projects/rdc/authentication/install_client.sh +++ b/projects/rdc/authentication/install_client.sh @@ -11,5 +11,7 @@ if [ $# -lt 1 ]; then fi INSTALL_DIR=$1 +mkdir -p $INSTALL_DIR cp -R client $INSTALL_DIR +chown -R rdc:rdc $INSTALL_DIR/client diff --git a/projects/rdc/authentication/install_server.sh b/projects/rdc/authentication/install_server.sh index 29364bb271..49a9ec0465 100755 --- a/projects/rdc/authentication/install_server.sh +++ b/projects/rdc/authentication/install_server.sh @@ -11,8 +11,11 @@ if [ $# -lt 1 ]; then fi INSTALL_DIR=$1 +mkdir -p $INSTALL_DIR cp -R server $INSTALL_DIR mkdir -p $INSTALL_DIR/client/certs cp client/certs/rdc_cacert.pem $INSTALL_DIR/client/certs chmod 700 $INSTALL_DIR/server/private +chown -R rdc:rdc $INSTALL_DIR/server +chown -R rdc:rdc $INSTALL_DIR/client diff --git a/projects/rdc/docs/README.md b/projects/rdc/docs/README.md index 4938249de5..ef0f6a4cc3 100755 --- a/projects/rdc/docs/README.md +++ b/projects/rdc/docs/README.md @@ -3,10 +3,6 @@ TODO: Add general description of RDC, link to github site -# Important note about Versioning and Backward Compatibility -RDC library is currently under development, and therefore subject to change either at the ABI or API level. The intention is to keep the API as stable as possible even while in development, but in some cases we may need to break backwards compatibility in order to ensure future stability and usability. Following [Semantic Versioning](https://semver.org/) rules, while the ROCm SMI library is in high state of change, the major version will remain 0, and backward compatibility is not ensured. - -Once new development has leveled off, the major version will become greater than 0, and backward compatibility will be enforced between major versions. # Building RDC @@ -55,10 +51,8 @@ TODO: THE REMAINDER NEEDS TO BE TAILORED FOR RDC To run the test, execute the program `rsmitst` that is built from the steps above. -# Usage Basics - -# Hello RDC +###Installation ### Authentication RDC supports encrypted communications between clients and servers. The communication can be configured to be authenticated or not authenticated. @@ -113,7 +107,8 @@ $ 02gen_ssl_artifacts.sh ``` At this point, the keys and certficates will be in the newly created "CA/artifacts" directory. This directory should be deleted if you need to rerun the scripts. -To install the scripts cd into the artifacts directory and run the install.sh script as root, specifying the install location. By default, RDC will expect this to be in /etc/rdc: +To install the keys and certificates, cd into the artifacts directory and run the install.sh script as root, specifying the install location. By default, RDC will expect this to be in /etc/rdc: + ```sh $ cd CA/artifacts $ sudo install_.sh /etc/rdc @@ -124,5 +119,55 @@ These files should be copied to and and installed on all client and server machi There are a few limitations on the authentication capabilities. These limitations are temporary and will be eliminated when the server has a configuration file where user preferences can be specified. * The client and server are hard-coded to look for openssl certificate and key files in /etc/rdc. +# Starting RDC Server Daemon (RDCD) +In order for an RDC client application to monitor and/or control a remote system, the RDC server daemon, rdcd, must be running on the remote system. rdcd can be configured to run with full capabilities, or with monitoring-only capabilities. "Full capabilities" includes the abilty to set some system functions exposed by the RDC APIs and tools. Changing a system's configuration involves writing to system files. When rdcd is configured to run with full capabilities, it has the ability to write to these system files. Alternatively, rdcd can be run with control functionality disabled. In this case, rdcd does not have the ability to write to the control-related system files. Calls to RDC APIs (or tools that invoke these APIs) will result in a permission-related failure when configured for limited functionality. This reduced mode can be used to prevent someone from inadvertantly or maliciously putting a system into an unwanted state. + +Which configuration is used depends on how rdcd is started, and certain settings in the rdc.service systemd configuration file. + +###Starting rdcd with systemctl +rdcd can be started by using the systemctl command. + +#####Starting rdcd with systemctl +When starting rdcd using systemctl, like this: + +```sh +$ systemctl start rdc +``` +systemctl will read /lib/systemd/system/rdc.service, which is installed with rdc. This file has 2 lines that control what capabilities with which rdcd will run. If left uncommented, rdcd will run with full capabilities, as shown below: + +```sh +CapabilityBoundingSet=CAP_DAC_OVERRIDE +AmbientCapabilities=CAP_DAC_OVERRIDE +``` + +When these lines are commented with `#`, rdcd will run with monitor-only capabilty. +###Starting rdcd Directly +rdcd can also be started by directly invoking rdcd from the command line, like this: + +```sh +# Start as user rdc +$ sudo -u rdc rdcd +``` + +```sh +# Start as root +$ sudo rdcd +``` + +Note how rdcd must be started as user "rdc" or root. Other regular user accounts will not work. This is because rdcd will need access to private SSL keys and certificates, owned by rdc. + +When run from the command line, the rdc.service file mentioned in the previous section does not come into play. What determines the level of capability is the level of capability of the id under which rdcd is started. If rdcd is directly started as root, then rdcd will have monitor and control capability. If rdcd is directly started with a normal user account, then it will have monitor-only capability. + + + +###Other Notes about rdcd +* Only 1 instance of rdcd should be running at a time. +* rdcd runs under the "rdc" user id + + +###Running with Monitor-Only Capability ("Limited Capability") + +# Hello RDC + diff --git a/projects/rdc/include/rdc/rdc.h b/projects/rdc/include/rdc/rdc.h old mode 100644 new mode 100755 index 53a5eb8dd1..e1674a4e22 --- a/projects/rdc/include/rdc/rdc.h +++ b/projects/rdc/include/rdc/rdc.h @@ -20,8 +20,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef RDC_RDC_H_ -#define RDC_RDC_H_ +#ifndef INCLUDE_RDC_RDC_H_ +#define INCLUDE_RDC_RDC_H_ #include @@ -740,4 +740,4 @@ const char* rdc_status_string(rdc_status_t status); */ const char* field_id_string(uint32_t field_id); -#endif // RDC_RDC_H_ +#endif // INCLUDE_RDC_RDC_H_ diff --git a/projects/rdc/server/src/rdc_server_main.cc b/projects/rdc/server/src/rdc_server_main.cc index 29af80aaae..8d5adbfa55 100755 --- a/projects/rdc/server/src/rdc_server_main.cc +++ b/projects/rdc/server/src/rdc_server_main.cc @@ -30,7 +30,7 @@ THE SOFTWARE. #include #include #include - +#include #include #include #include @@ -58,7 +58,8 @@ static bool sShutDownServer = false; static bool sRestartServer = false; static const char *kDaemonName = "rdcd"; static const char *kRDCDHomeDir = "/"; -static const char *kDaemonLockFile = "/var/run/rdcd.lock"; +static const char *kDaemonLockFileRoot = "/var/run/rdcd.lock"; +static const char *kDaemonLockFile = "/tmp/rdcd.lock"; // Pinned certificates static const char * kDefaultRDCServerCertPinPath = @@ -77,6 +78,7 @@ static const char * kDefaultRDCClientCACertPemPkiPath = "/etc/rdc/client/certs/rdc_cacert.pem"; static const char *kDefaultListenPort = "50051"; +static const uint32_t kRSMIUMask = 027; RDCServer::RDCServer() : server_address_("0.0.0.0:"), secure_creds_(false), rsmi_service_(nullptr), rdc_admin_service_(nullptr) { @@ -269,6 +271,61 @@ static void InitializeSignalHandling(void) { signal(SIGTERM, HandleSignal); } +static int +FileOwner(const char *fn, std::string *owner) { + struct stat info; + int ret; + + assert(owner); + if (owner == nullptr) { + return EINVAL; + } + ret = stat(fn, &info); + if (ret) { + perror("Failed to stat lock file"); + return errno; + } + struct passwd pw; + struct passwd *result; + char buf[20]; + + ret = getpwuid_r(info.st_uid, &pw, buf, 20, &result); + if (ret == 0) { + *owner = buf; + } else { + return ret; + perror("Failed to determine owner of file"); + } + return 0; +} + +static int UserID(const char *un, uid_t *uid) { + int ret; + struct passwd pw; + struct passwd *result; + char *buf; + int bufsize; + + assert(uid != nullptr); + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize == -1) { + bufsize = 16384; + } + buf = new char[bufsize]; + + ret = getpwnam_r(un, &pw, buf, bufsize, &result); + delete []buf; + + if (ret == 0) { + *uid = pw.pw_uid; + } else { + perror("Failed to determine user id for given name"); + return 1; + } + return 0; +} + void RDCServer::ShutDown(void) { server_->Shutdown(); @@ -310,35 +367,106 @@ static void * ProcessSignalLoop(void *server_ptr) { pthread_exit(0); } -static void ExitIfAlreadyRunning(void) { - int single_proc_fh; - ssize_t fsz; +static bool FileIsLocked(std::string fn) { + struct flock fl; + int fh; + int ret; - single_proc_fh = open(kDaemonLockFile, O_RDWR|O_CREAT, 0640); - if (single_proc_fh < 0) { - std::cerr << "Failed to open file lock:" << kDaemonLockFile << std::endl; + auto close_fh = [&]() { + ret = close(fh); + if (ret) { + perror(fn.c_str()); + } + }; + + (void)memset(&fl, 0, sizeof(struct flock)); + errno = 0; + fh = open(fn.c_str(), O_RDONLY); + if (fh == -1 && errno == ENOENT) { + // In this case, there was no previous lock. + close_fh(); + return false; + } + + ret = fcntl(fh, F_GETLK, &fl); + if (ret) { + perror("Failed to get file-lock status. Is rdcd already running?"); exit(1); } - if (lockf(single_proc_fh, F_TLOCK, 0) < 0) { + if (fl.l_type == F_UNLCK) { + close_fh(); + return false; + } + + close_fh(); + return true; +} + +static void ExitIfAlreadyRunning(bool is_root) { + const char *lock_fn; + int lock_fh; + std::string lf_user(kDaemonLockFile); + std::string lf_root(kDaemonLockFileRoot); + ssize_t fsz; + + auto chk_if_locked = [&](std::string lock_file) { + bool is_locked = FileIsLocked(lock_file); + + if (is_locked) { + std::cerr << "File " << lock_file << + " is locked. Is rdcd already running?" << std::endl; + exit(1); + } + }; + + chk_if_locked(lf_root); + chk_if_locked(lf_user); + + if (is_root) { + lock_fn = kDaemonLockFileRoot; + } else { + lock_fn = kDaemonLockFile; + } + // Temporarily adjust file-mask to create file with right permissions + umask(023); + lock_fh = open(lock_fn, O_RDWR|O_CREAT, 0644); + + if (lock_fh < 0) { + std::string user; + int ret = FileOwner(lock_fn, &user); + if (ret) { + perror("Failed to determine owner of lock file."); + exit(ret); + } + std::cerr << "Failed to open file lock:" << lock_fn << " owned by user: " + << user << ". If starting rdcd as a different user, delete this " + "lock-file first." << std::endl; + // asserting below since this should have been prevented in main() + assert(!"Unexpected user invoking rdcd"); + exit(1); + } + + if (lockf(lock_fh, F_TLOCK, 0) < 0) { std::cerr << "Daemon already running. Exiting this instance." << std::endl; exit(0); } + umask(kRSMIUMask); std::string pid_str = std::to_string(getpid()); - fsz = write(single_proc_fh, pid_str.c_str(), pid_str.size()); + fsz = write(lock_fh, pid_str.c_str(), pid_str.size()); assert(static_cast(fsz) == pid_str.size()); } static void -MakeDaemon() { +MakeDaemon(bool is_root) { int fd0; struct rlimit max_files; // RSMI, for one thing, will need to be able to read/write files // Note that umask turns *off* permission for a given bit, so you we want // the complement of the permissions we want files to have. - umask(027); + umask(kRSMIUMask); // To Do; Make this optional based on CL option. By default, don't do this. // Instead rely on serviced to make it a daemon. @@ -390,7 +518,7 @@ MakeDaemon() { exit(1); } - ExitIfAlreadyRunning(); + ExitIfAlreadyRunning(is_root); InitializeSignalHandling(); } @@ -419,9 +547,9 @@ static void PrintHelp(void) { "default is to listen on port 50051\n" "--unauth_comm, -u don't do authentication with communications" " with client. When this flag is not specified, by default, " - "PKI authentication is used\n" + "PKI authentication is used\n" "--pinned_cert, -i used \"pinned\" certificates instead of PKI " - "authentication. This is for test purposes.\n" + "authentication. This is for test purposes.\n" "--debug, -d output debug messages\n" "--help, -h print this message\n"; } @@ -495,11 +623,27 @@ int main(int argc, char** argv) { RDCServer rdc_server; RdcdCmdLineOpts cmd_line_opts; int err; + uid_t rdc_uid; + uid_t caller_id = geteuid(); + + bool is_root = (caller_id == 0); + + if (!is_root) { + // Ensure user is calling as "rdc" + err = UserID("rdc", &rdc_uid); + if (err != 0) { + return 1; + } + if (rdc_uid != caller_id) { + std::cerr << "Only user \"rdc\" or root can start rdcd." << std::endl; + exit(1); + } + } init_cmd_line_opts(&cmd_line_opts); ProcessCmdline(&cmd_line_opts, argc, argv); - MakeDaemon(); + MakeDaemon(is_root); rdc_server.Initialize(&cmd_line_opts); @@ -511,23 +655,23 @@ int main(int argc, char** argv) { std::cerr << "Failed to get capability" << std::endl; return 1; } - if (!cap_enabled) { - std::cerr << - "Expected CAP_DAC_OVERRIDE CAP_EFFECTIVE to be enabled, but it not." << - std::endl; - return 1; - } - err = amd::rdc::GetCapability(CAP_DAC_OVERRIDE, CAP_PERMITTED, &cap_enabled); - if (err) { - std::cerr << "Failed to get capability" << std::endl; - return 1; + if (cap_enabled) { + err = + amd::rdc::GetCapability(CAP_DAC_OVERRIDE, CAP_PERMITTED, &cap_enabled); + if (err) { + std::cerr << "Failed to get capability" << std::endl; + return 1; + } + if (!cap_enabled) { + std::cerr << + "CAP_DAC_OVERRIDE CAP_PERMITTED is not enabled" << std::endl; + } + } else { + std::cerr << "CAP_DAC_OVERRIDE CAP_EFFECTIVE is not enabled." << std::endl; } if (!cap_enabled) { - std::cerr << - "Expected CAP_DAC_OVERRIDE CAP_PERMITTED to be enabled, but it not." << - std::endl; - return 1; + std::cerr << "rdcd functionality is limited to read-only" << std::endl; } // Don't allow rwx access to all files to ever be inheritable. We may need