diff --git a/Makefile b/Makefile index 9644f129e..4535b82ee 100644 --- a/Makefile +++ b/Makefile @@ -81,11 +81,7 @@ tools/hawk_monitor: tools/hawk_monitor.c $(shell pkg-config --libs glib-2.0) \ $(shell pkg-config --libs libxml-2.0) -# TODO(must): This is inching towards becoming annoying: want better build infrastructure/deps -tools/hawk_invoke: tools/hawk_invoke.c tools/common.h - gcc -fpie -pie $(CFLAGS) -o $@ $< - -tools: tools/hawk_chkpwd tools/hawk_monitor tools/hawk_invoke +tools: tools/hawk_chkpwd tools/hawk_monitor base/install: mkdir -p $(DESTDIR)$(WWW_BASE)/hawk/log @@ -118,10 +114,6 @@ tools/install: -chown root.haclient $(DESTDIR)/usr/sbin/hawk_chkpwd || true -chmod u+s $(DESTDIR)/usr/sbin/hawk_chkpwd - install -D -m 4750 tools/hawk_invoke $(DESTDIR)/usr/sbin/hawk_invoke - -chown root.haclient $(DESTDIR)/usr/sbin/hawk_invoke || true - -chmod u+s $(DESTDIR)/usr/sbin/hawk_invoke - install -D -m 0755 tools/hawk_monitor $(DESTDIR)/usr/sbin/hawk_monitor # TODO(should): Verify this is really clean (it won't get rid of .mo files, @@ -132,7 +124,6 @@ clean: rm -f scripts/hawk.{suse,redhat,service} rm -f tools/hawk_chkpwd rm -f tools/hawk_monitor - rm -f tools/hawk_invoke rm -f tools/common.h # Note: chown & chmod here are only necessary if *not* doing an RPM build diff --git a/hawk/app/models/cib.rb b/hawk/app/models/cib.rb index db3901d3f..290fc151d 100644 --- a/hawk/app/models/cib.rb +++ b/hawk/app/models/cib.rb @@ -501,7 +501,7 @@ def initialize(id, user, use_file = false, stonithwarning = false) init_offline_cluster id, user, use_file return end - out, err, status = Util.run_as(user, 'cibadmin', '-Ql') + out, err, status = Util.capture3('cibadmin', '-Ql') case status.exitstatus when 0 @xml = REXML::Document.new(out) diff --git a/hawk/app/models/cluster.rb b/hawk/app/models/cluster.rb index 6e96d4ccf..664e7020e 100644 --- a/hawk/app/models/cluster.rb +++ b/hawk/app/models/cluster.rb @@ -102,8 +102,7 @@ def cluster_copy(clusters) fname = "#{Rails.root}/tmp/dashboard.js" File.open(fname, "w") { |f| f.write(JSON.pretty_generate(clusters)) } File.chmod(0660, fname) - out, err, rc = Util.run_as("root", "crm", "cluster", "copy", fname) - out, err, rc = Util.run_as("root", "crm", "cluster", "run", "chown hacluster:haclient #{fname}") if rc == 0 + out, err, rc = Util.capture3("crm", "cluster", "copy", fname) Rails.logger.debug "Copy: #{out} #{err} #{rc}" # always succeed here: we don't really care that much if the copy succeeded or not true diff --git a/hawk/app/models/report.rb b/hawk/app/models/report.rb index 5e537af19..8b9554d39 100644 --- a/hawk/app/models/report.rb +++ b/hawk/app/models/report.rb @@ -144,7 +144,7 @@ def graph(hb_report, path, format = :svg) require "tempfile" tmpfile = Tempfile.new("hawk_dot") tmpfile.close - _out, err, status = Util.run_as('hacluster', 'crm_simulate', '-x', tpath.to_s, format == :xml ? "-G" : "-D", tmpfile.path.to_s) + _out, err, status = Util.capture3('crm_simulate', '-x', tpath.to_s, format == :xml ? "-G" : "-D", tmpfile.path.to_s) rc = status.exitstatus ret = [false, err] diff --git a/hawk/lib/hb_report.rb b/hawk/lib/hb_report.rb index 9f42ffbf9..e74a4577f 100644 --- a/hawk/lib/hb_report.rb +++ b/hawk/lib/hb_report.rb @@ -99,7 +99,7 @@ def generate(from_time, to_time, all_nodes = true) args.push("-S") unless all_nodes args.push(@path) - out, err, status = Util.run_as("root", "crm", "report", *args) + out, err, status = Util.capture3('crm', "report", *args) f = File.new(@outfile, "w") f.write(out) f.close diff --git a/hawk/lib/invoker.rb b/hawk/lib/invoker.rb index 3ae62722b..b6049dd7d 100644 --- a/hawk/lib/invoker.rb +++ b/hawk/lib/invoker.rb @@ -27,7 +27,7 @@ def initialize # cleaned up further) # Returns [out, err, exitstatus] def run(*cmd) - out, err, status = Util.run_as(current_user, *cmd) + out, err, status = Util.capture3(*cmd) [out, fudge_error(status.exitstatus, err), status.exitstatus] end @@ -73,7 +73,7 @@ def crm_configure_load_update(cmd) # Invoke cibadmin with command line arguments. Returns stdout as string, # Raises NotFoundError, SecurityError or RuntimeError on failure. def cibadmin(*cmd) - out, err, status = run_as current_user, 'cibadmin', *cmd + out, err, status = Util.capture3('cibadmin', *cmd) case status.exitstatus when 0 return out @@ -105,7 +105,7 @@ def cibadmin_modify(xml) # Used by the simulator def crm_simulate(*cmd) - run_as current_user, 'crm_simulate', *cmd + Util.capture3('crm_simulate', *cmd) end private @@ -131,7 +131,7 @@ def invoke_crm(input, *cmd) end end cmd << { stdin_data: input } - out, err, status = run_as current_user, 'crm', *cmd + out, err, status = Util.capture3('crm', *cmd) [out, fudge_error(status.exitstatus, err), status.exitstatus] end diff --git a/hawk/lib/util.rb b/hawk/lib/util.rb index 3cb0e65ef..a096e8a51 100644 --- a/hawk/lib/util.rb +++ b/hawk/lib/util.rb @@ -110,23 +110,6 @@ def ensure_home_for(user) end module_function :ensure_home_for - # Like capture3, but via /usr/sbin/hawk_invoke - def run_as(user, *cmd) - Rails.logger.debug "Executing `#{cmd.join(' ').inspect}` through `run_as`" - old_home = ensure_home_for(user) - # RORSCAN_INL: multi-arg invocation safe from shell injection. - ret = capture3('/usr/sbin/hawk_invoke', user, *cmd) - # Having invoked a command, reset $HOME to what it was before, - # else it sticks, and other (non-invoker) crm invoctiaons, e.g. - # has_feature() run the shell as hacluster, which in turn causes - # $HOME/.cache and $HOME/.config to revert to 600 with uid hacluster, - # which means the *next* call after that will die with permission - # problems, and you will spend an entire day debugging it. - ENV['HOME'] = old_home - ret - end - module_function :run_as - def diff(a, b) # call diff on a and b # returns [data, ok?] diff --git a/tools/hawk_invoke.c b/tools/hawk_invoke.c deleted file mode 100644 index 411147a26..000000000 --- a/tools/hawk_invoke.c +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (c) 2011-2013 SUSE LLC, All Rights Reserved. - * See COPYING for license. - * - * Author: Tim Serong - * - */ - -/* - * hawk_invoke allows the hacluster user to run a small assortment of - * Pacemaker CLI tools as another user, in order to support Pacemaker's - * ACL feature. - * - * hawk_invoke: - * - must be installed setuid root - * - will refuse to run if invoked by anyone other than "root" or - * "hacluster" - * - will only setuid() to a non-root user in the "haclient" group. - * - will in turn only invoke a specific small set of Pacemaker - * CLI commands. - * - * The idea here is that hawk_invoke: - * - Will allow "hacluster" or "root" to become a less-privileged - * user for the purposes of cluster administration. - * - Will not allow the "hacluster" user to become a more-privileged - * user. - * - The only exception here is for the execution of hb_report and - * "crm history", which must be run as "root", or they won't work. - * also adds an exception for "crm cluster copy ???/tmp/dashboard.js" - * - Will not allow arbitrary commands to be executed as any other - * user. - * - * Usage: - * - * /usr/sbin/hawk_invoke [args ...] - * - * Where: - * - * username = name of the user to run command as - * command = short name of command (e.g.: "crm_mon") - * args = any args for command - * - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "common.h" - -struct cmd_map -{ - const char *name; /* Short name of command */ - const char *path; /* Full path to actual command */ -}; - -static struct cmd_map commands[] = { - {"cibadmin", SBINDIR"/cibadmin"}, - {"crm", SBINDIR"/crm"}, - {"crmadmin", SBINDIR"/crmadmin"}, - {"crmd", LIBDIR"/heartbeat/crmd"}, - {"crm_attribute", SBINDIR"/crm_attribute"}, - {"crm_mon", SBINDIR"/crm_mon"}, - {"crm_shadow", SBINDIR"/crm_shadow"}, - {"crm_simulate", SBINDIR"/crm_simulate"}, - {"pengine", LIBDIR"/heartbeat/pengine"}, - {"hb_report", SBINDIR"/hb_report"}, - {"booth", SBINDIR"/booth"}, - {NULL, NULL} -}; - -#define DASHBOARD_FILE "/tmp/dashboard.js" - - -static void die(const char *format, ...) -{ - va_list args; - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - exit(1); -} - -static int strendswith(char* str, char* tail) -{ - size_t l = strlen(str), t = strlen(tail); - return l >= t && strncmp(str + l - t, tail, t) == 0; -} - -static int valid_dashboard_file(char* str) -{ - char* s; - for (s = str; *s; ++s) { - if (*s == '.' && *(s + 1) == '.') { - return 0; - } else if (!isalnum(*s) && strchr(" :/_-.", *s) == NULL) { - return 0; - } - } - return strendswith(str, DASHBOARD_FILE); -} - -/* - * Allow chmod hacluster:haclient /tmp/dashboard.js - * and as little else as possible - */ -static int valid_run_command(char* str) -{ - #define TMPSTR_SIZE 512 - char tmp[TMPSTR_SIZE]; - size_t tmplen; - memset(tmp, 0, TMPSTR_SIZE); - snprintf(tmp, sizeof(tmp), "chown %s:%s ", HACLUSTER, HACLIENT); - tmplen = strlen(tmp); - if (strncmp(str, tmp, tmplen) != 0) - return 0; - return valid_dashboard_file(str + tmplen); -} - -static int allow_root(int argc, char** argv) -{ - if (strcmp(argv[2], "hb_report") == 0) - return 1; - if (argc >= 4 && - strcmp(argv[2], "crm") == 0 && - strcmp(argv[3], "report") == 0) - return 1; - if (argc >= 4 && - strcmp(argv[2], "crm") == 0 && - strcmp(argv[3], "history") == 0) - return 1; - if (argc == 6 && - strcmp(argv[2], "crm") == 0 && - strcmp(argv[3], "cluster") == 0 && - strcmp(argv[4], "copy") == 0 && - valid_dashboard_file(argv[5])) - return 1; - if (argc == 6 && - strcmp(argv[2], "crm") == 0 && - strcmp(argv[3], "cluster") == 0 && - strcmp(argv[4], "run") == 0 && - valid_run_command(argv[5])) - return 1; - return 0; -} - -int main(int argc, char **argv) -{ - uid_t uid; - struct passwd *pwd; - struct group *grp; - char *grp_user; - int i; - int found = 0; - struct cmd_map *cmd; - char *home = NULL; - char *cib_shadow = NULL; - - if (argc < 3) { - die("Usage: %s [args ...]\n", argv[0]); - } - - /* Ensure we're being run by either root or hacluster */ - uid = getuid(); - if (uid != 0) { - /* Not root, let's see if we're running as hacluster */ - pwd = getpwuid(uid); - - #if WITHIN_VAGRANT == 1 - if (pwd == NULL || (strcmp(pwd->pw_name, HACLUSTER) != 0 && strcmp(pwd->pw_name, VAGRANT) != 0)) { - #else - if (pwd == NULL || strcmp(pwd->pw_name, HACLUSTER) != 0) { - #endif - /* - * Not hacluster either. - * TODO: log this to syslog, to alert sysadmin - * of potential nefarious local user. - */ - die("ERROR: Permission denied\n"); - } - } - - /* See who we're trying to become... */ - pwd = getpwnam(argv[1]); - if (pwd == NULL) { - die("ERROR: User '%s' not found\n", argv[1]); - } - - if ((pwd->pw_uid == 0 || strcmp(pwd->pw_name, "root") == 0) && allow_root(argc, argv)) { - - /* - * Special case to become root when running hb_report - * or "crm history", and we force group to HACLIENT. - */ - grp = getgrnam(HACLIENT); - - } else { - - /* - * Don't become root! - * (Is there really any sense checking for pw_name == "root"? - */ - if (pwd->pw_uid == 0 || strcmp(pwd->pw_name, "root") == 0) { - die("ERROR: Thou shalt not become root\n"); - } - - /* Make sure the new user is in the haclient group */ - grp = getgrgid(pwd->pw_gid); - if (grp == NULL || strcmp(grp->gr_name, HACLIENT) != 0) { - /* Not the primary group, let's check the others */ - grp = getgrnam(HACLIENT); - if (grp == NULL) { - die("ERROR: Group '%s' does not exist\n", HACLIENT); - } - i = 0; - found = 0; - while ((grp_user = grp->gr_mem[i]) != NULL) { - if (strcmp(grp_user, pwd->pw_name) == 0) { - found = 1; - break; - } - i++; - } - if (!found) { - die("ERROR: User '%s' is not in the '%s' group\n", pwd->pw_name, HACLIENT); - } - } - } - - /* Verify the command to execute is valid, and expand it */ - found = 0; - for (cmd = commands; cmd->name != NULL; cmd++) { - if (strcmp(cmd->name, argv[2]) == 0) { - found = 1; - break; - } - } - if (!found) { - die("ERROR: Invalid command '%s'\n", argv[2]); - } - /* (bit rough to drop const...) */ - argv[2] = (char *)cmd->path; - - /* - * Drop extraneous groups. Not doing this is a security issue. - * See POS36-C. - * - * This will fail if we aren't root, so don't bother checking - * the return value, this is just done as an optimistic privilege - * dropping function. - */ - { - int save_errno = errno; - setgroups(0, NULL); - errno = save_errno; - } - - /* - * Become the new user. Note that at this point, grp still refers - * to the "haclient" group, either because that was the user's - * primary group, or because we looked that group up to search - * through its members. Likewise pwd still refers to the user - * we're trying to become. - */ - if (setresgid(grp->gr_gid, grp->gr_gid, grp->gr_gid) != 0) { - die("ERROR: Can't set group to '%s' (%d)\n", grp->gr_name, grp->gr_gid); - } - if (setresuid(pwd->pw_uid, pwd->pw_uid, pwd->pw_uid) != 0) { - die("ERROR: Can't set user to '%s' (%d)\n", pwd->pw_name, pwd->pw_uid); - } - - /* - * Bit of cleanup - is this in the right place, and is it really - * necessary? - */ - endpwent(); - endgrent(); - - /* Clean up environment */ - home = getenv("HOME"); - if (home != NULL) { - home = strdup(home); - } - cib_shadow = getenv("CIB_shadow"); - if (cib_shadow != NULL) { - cib_shadow = strdup(cib_shadow); - } - if (clearenv() != 0) { - die("ERROR: Can't clear environment"); - } - setenv("PATH", SBINDIR":"BINDIR":/bin", 1); - if (home != NULL) { - setenv("HOME", home, 1); - } - if (cib_shadow != NULL) { - setenv("CIB_shadow", cib_shadow, 1); - } - - /* And away we go... */ - execv(argv[2], &argv[2]); - perror(argv[2]); - return 1; -}