summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCsaba Henk <csaba@gluster.com>2011-09-11 19:45:57 +0200
committerVijay Bellur <vijay@gluster.com>2011-09-22 05:23:32 -0700
commit2ab00369e7ef99d287dad5301d2f334dcfd67a70 (patch)
treec4e7c2ac9f3285f6d2617133b131d924044dc786
parent1098aaa51d2e3dca9e6c48ee1e9cb43bc87936f4 (diff)
gsyncd: implement restricted mode and utility dispatch
With this change, the suggested way of setting up a geo-sync slave is to use an ssh key with gsyncd as a forced command (see sshd(8)), or set gsyncd as shell. This prevents the master in executing arbitrary commands on slave (a major security hole). Detailed list the changes: - All gsyncd invocations that are not done by glusterd are considered unsafe and then we operate in so-called "restricted mode" (see below) - if we are invoked on purpose (ie. it's not the case that sshd forced us to run as frontend of a remote-invoked command), we execute gsyncd.py - if invoked by sshd as frontend command, we check the remote command line and call the required utility if it's among the allowed ones (rsyncd and gsyncd) - with rsync, we check if invocation is server mode and some other sanity measures - with gsyncd, in restricted mode we enforce the usage of the glusterd provided config file, and in python, we enforce operation in server mode and some other sanity checks Impact on using geo-rep the old way: remote file slave now also requires a running glusterd (to pick up config from). Missing: we not implemented check of the rsync target path. The issue of master being able to modify arbitrary locations is planned to be mitigated by using geo-rep with an unprivileged user. Change-Id: I9b5825bfe282a9ca777429aadd554d78708f1638 BUG: 2825 Reviewed-on: http://review.gluster.com/460 Tested-by: Gluster Build System <jenkins@build.gluster.com> Reviewed-by: Vijay Bellur <vijay@gluster.com>
-rw-r--r--xlators/features/marker/utils/src/gsyncd.c205
-rw-r--r--xlators/features/marker/utils/syncdaemon/gsyncd.py15
2 files changed, 182 insertions, 38 deletions
diff --git a/xlators/features/marker/utils/src/gsyncd.c b/xlators/features/marker/utils/src/gsyncd.c
index ec1d3a65a98..cebca1aeaae 100644
--- a/xlators/features/marker/utils/src/gsyncd.c
+++ b/xlators/features/marker/utils/src/gsyncd.c
@@ -32,52 +32,67 @@
#include "run.h"
#define _GLUSTERD_CALLED_ "_GLUSTERD_CALLED_"
+#define _GSYNCD_DISPATCHED_ "_GSYNCD_DISPATCHED_"
#define GSYNCD_CONF "geo-replication/gsyncd.conf"
+#define RSYNC "rsync"
+
+int restricted = 0;
static int
-config_wanted (int argc, char **argv)
+duplexpand (void **buf, size_t tsiz, size_t *len)
{
- char *evas = NULL;
- char *oa = NULL;
- int i = 0;
- int one_more_arg = 0;
+ size_t osiz = tsiz * *len;
- evas = getenv (_GLUSTERD_CALLED_);
- if (evas && strcmp (evas, "1") == 0) {
- /* OK, we know glusterd called us, no need to look for further config
- * ... altough this conclusion should not inherit to our children
- */
- unsetenv (_GLUSTERD_CALLED_);
- return 0;
- }
+ *buf = realloc (*buf, osiz << 1);
+ if (!buf)
+ return -1;
- for (i = 1; i < argc; i++) {
- /* -c found, see if it has an argument */
- if (one_more_arg) {
- if (argv[i][0] != '-')
- return 0;
- one_more_arg = 0;
- }
+ memset ((char *)*buf + osiz, 0, osiz);
+ *len <<= 1;
- if ((strcmp (argv[i], "-c") && strcmp (argv[i], "--config-file")) == 0) {
- one_more_arg = 1;
- continue;
- }
+ return 0;
+}
+
+static int
+str2argv (char *str, char ***argv)
+{
+ char *p = NULL;
+ int argc = 0;
+ size_t argv_len = 32;
+ int ret = 0;
+
+ assert (str);
+ str = strdup (str);
+ if (!str)
+ return -1;
- oa = strtail (argv[i], "-c");
- if (oa && !*oa)
- oa = NULL;
- if (!oa)
- oa = strtail (argv[i], "--config-file=");
- if (oa)
- return 0;
+ *argv = calloc (argv_len, sizeof (**argv));
+ if (!*argv)
+ goto error;
+
+ while ((p = strtok (str, " "))) {
+ str = NULL;
+
+ argc++;
+ if (argc == argv_len) {
+ ret = duplexpand ((void *)argv,
+ sizeof (**argv),
+ &argv_len);
+ if (ret == -1)
+ goto error;
+ }
+ (*argv)[argc - 1] = p;
}
- return 1;
+ return argc;
+
+ error:
+ fprintf (stderr, "out of memory\n");
+ return -1;
}
-int
-main(int argc, char **argv)
+static int
+invoke_gsyncd (int argc, char **argv)
{
char config_file[PATH_MAX] = {0,};
size_t gluster_workdir_len = 0;
@@ -86,7 +101,8 @@ main(int argc, char **argv)
int j = 0;
char *nargv[argc + 4];
- if (config_wanted (argc, argv)) {
+ if (restricted) {
+ /* in restricted mode we forcibly use the system-wide config */
runinit (&runner);
runner_add_args (&runner, SBIN_DIR"/gluster",
"--log-file=/dev/stderr", "system::", "getwd",
@@ -106,18 +122,21 @@ main(int argc, char **argv)
config_file[gluster_workdir_len] = '/';
strcat (config_file, GSYNCD_CONF);
} else
- config_file[0] = '\0';
+ goto error;
+
+ if (setenv ("_GSYNCD_RESTRICTED_", "1", 1) == -1)
+ goto error;
}
j = 0;
nargv[j++] = PYTHON;
nargv[j++] = GSYNCD_PREFIX"/python/syncdaemon/gsyncd.py";
+ for (i = 1; i < argc; i++)
+ nargv[j++] = argv[i];
if (config_file[0]) {
nargv[j++] = "-c";
nargv[j++] = config_file;
}
- for (i = 1; i < argc; i++)
- nargv[j++] = argv[i];
nargv[j++] = NULL;
execvp (PYTHON, nargv);
@@ -129,3 +148,113 @@ main(int argc, char **argv)
fprintf (stderr, "gsyncd initializaion failed\n");
return 1;
}
+
+static int
+invoke_rsync (int argc, char **argv)
+{
+ int i = 0;
+
+ assert (argv[argc] == NULL);
+
+ if (argc < 2 || strcmp (argv[1], "--server") != 0)
+ goto error;
+
+ for (i = 2; i < argc && argv[i][0] == '-'; i++);
+
+ if (!(i == argc ||
+ (i == argc - 2 && strcmp (argv[i], ".") == 0 && argv[i + 1][0] == '/')))
+ goto error;
+
+ /* XXX a proper check would involve the following:
+ * - require rsync to not protect args (ie. pass target in command line)
+ * - find out proper synchronization target by:
+ * - looking up sshd process we origin from
+ * - within its children, find the gsyncd process
+ * that maintains the aux mount
+ * - find out mount directory by checking the working directory
+ * of the gsyncd process
+ * - demand that rsync target equals to sync target
+ *
+ * As of now, what we implement is dispatching rsync invocation to
+ * our system rsync, that handles the cardinal issue of controlling
+ * remote-requested command invocations.
+ */
+
+ argv[0] = RSYNC;
+
+ execvp (RSYNC, argv);
+
+ fprintf (stderr, "exec of "RSYNC" failed\n");
+ return 127;
+
+ error:
+ fprintf (stderr, "disallowed "RSYNC" invocation\n");
+ return 1;
+}
+
+struct invocable {
+ char *name;
+ int (*invoker) (int argc, char **argv);
+};
+
+struct invocable invocables[] = {
+ { "rsync", invoke_rsync },
+ { "gsyncd", invoke_gsyncd },
+ { NULL, NULL}
+};
+
+int
+main (int argc, char **argv)
+{
+ char *evas = NULL;
+ struct invocable *i = NULL;
+ char *b = NULL;
+ char *sargv = NULL;
+
+ evas = getenv (_GLUSTERD_CALLED_);
+ if (evas && strcmp (evas, "1") == 0)
+ /* OK, we know glusterd called us, no need to look for further config
+ * ... altough this conclusion should not inherit to our children
+ */
+ unsetenv (_GLUSTERD_CALLED_);
+ else {
+ /* we regard all gsyncd invocations unsafe
+ * that do not come from glusterd and
+ * therefore restrict it
+ */
+ restricted = 1;
+
+ if (!getenv (_GSYNCD_DISPATCHED_)) {
+ evas = getenv ("SSH_ORIGINAL_COMMAND");
+ if (evas)
+ sargv = evas;
+ else {
+ evas = getenv ("SHELL");
+ if (evas && strcmp (basename (evas), "gsyncd") == 0 &&
+ argc == 3 && strcmp (argv[1], "-c") == 0)
+ sargv = argv[2];
+ }
+ }
+
+ }
+
+ if (!(sargv && restricted))
+ return invoke_gsyncd (argc, argv);
+
+ argc = str2argv (sargv, &argv);
+ if (argc == -1 || setenv (_GSYNCD_DISPATCHED_, "1", 1) == -1) {
+ fprintf (stderr, "internal error\n");
+ return 1;
+ }
+
+ b = basename (argv[0]);
+ for (i = invocables; i->name; i++) {
+ if (strcmp (b, i->name) == 0)
+ return i->invoker (argc, argv);
+ }
+
+ fprintf (stderr, "invoking %s in restricted SSH session is not allowed\n",
+ b);
+
+ return 1;
+}
diff --git a/xlators/features/marker/utils/syncdaemon/gsyncd.py b/xlators/features/marker/utils/syncdaemon/gsyncd.py
index 9cae4d407f4..6747acbce6f 100644
--- a/xlators/features/marker/utils/syncdaemon/gsyncd.py
+++ b/xlators/features/marker/utils/syncdaemon/gsyncd.py
@@ -190,6 +190,8 @@ def main_i():
op.add_option('--canonicalize-escape-url', dest='url_print', action='callback', callback=store_local_curry('canon_esc'))
tunables = [ norm(o.get_opt_string()[2:]) for o in op.option_list if o.callback in (store_abs, 'store_true', None) and o.get_opt_string() not in ('--version', '--help') ]
+ remote_tunables = [ 'listen', 'go_daemon', 'timeout', 'session_owner', 'config_file' ]
+ rq_remote_tunables = { 'listen': True }
# precedence for sources of values: 1) commandline, 2) cfg file, 3) defaults
# -- for this to work out we need to tell apart defaults from explicitly set
@@ -206,6 +208,19 @@ def main_i():
sys.stderr.write(op.get_usage() + "\n")
sys.exit(1)
+ if os.getenv('_GSYNCD_RESTRICTED_'):
+ allopts = {}
+ allopts.update(opts.__dict__)
+ allopts.update(rconf)
+ bannedtuns = set(allopts.keys()) - set(remote_tunables)
+ if bannedtuns:
+ raise GsyncdError('following tunables cannot be set with restricted SSH invocaton: ' + \
+ ', '.join(bannedtuns))
+ for k, v in rq_remote_tunables.items():
+ if not k in allopts or allopts[k] != v:
+ raise GsyncdError('tunable %s is not set to value %s required for restricted SSH invocaton' % \
+ (k, v))
+
if getattr(confdata, 'rx', None):
# peers are regexen, don't try to parse them
canon_peers = args