diff options
author | Austin Adams <screamingmoron@gmail.com> | 2015-08-08 20:48:18 -0400 |
---|---|---|
committer | Austin Adams <screamingmoron@gmail.com> | 2015-08-08 20:48:18 -0400 |
commit | 5cda9dfa4bc94ab06e0da8186104119ac0006349 (patch) | |
tree | 4a65d360b50971ca5d4eea68182aea1e9d582c61 | |
download | nsdo-5cda9dfa4bc94ab06e0da8186104119ac0006349.tar.gz nsdo-5cda9dfa4bc94ab06e0da8186104119ac0006349.tar.xz |
initial commit
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | LICENSE | 20 | ||||
-rw-r--r-- | Makefile | 29 | ||||
-rw-r--r-- | README.md | 72 | ||||
-rw-r--r-- | nsdo.1 | 15 | ||||
-rw-r--r-- | nsdo.c | 204 | ||||
-rw-r--r-- | readme.head | 43 |
7 files changed, 386 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff5d630 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +nsdo +nsdo.1.gz +.*.sw? @@ -0,0 +1,20 @@ +Copyright (c) 2015 Austin Adams + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..390723a --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +CFLAGS ?= -Wall -Werror -O3 +PREFIX ?= /usr/local + +PROG = nsdo +MANSECTION = 1 +MANPAGE = $(PROG).$(MANSECTION) +MANPAGEGZ = $(MANPAGE).gz +README = README.md + +.PHONY: all install clean + +all: $(PROG) $(MANPAGEGZ) $(README) + +install: $(PROG) $(MANPAGEGZ) + install -Dm6755 $< $(PREFIX)/bin/$< + install -Dm644 $(word 2,$^) $(PREFIX)/share/man/man$(MANSECTION)/$(word 2,$^) + +clean: + rm -fv $(PROG) $(MANPAGEGZ) $(README) + +$(MANPAGEGZ): $(MANPAGE) + gzip --best -k $< + +$(README): readme.head $(MANPAGE) + { \ + cat $<; \ + echo; \ + MANWIDTH=68 man -l $(word 2,$^) | sed 's/^/ /'; \ + } >$@ diff --git a/README.md b/README.md new file mode 100644 index 0000000..99e3b98 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +nsdo +==== + +This is a simple C program that runs a command inside a given [Linux +network namespace][1]. + +Effectively, it simplifies: + + $ sudo ip netns exec myns sudo -u "$USER" myprogram + +to + + $ nsdo myns myprogram + +Thanks to magic of the [setuid bit][2], it initially has root +privileges, which allows it to change its own network namespace, +`setuid()` to the user who ran the command, and then `exec()` the +requested command. + +installation +------------ + +If you're on Arch, you can build [my AUR package][4]. + +Otherwise: + + $ make + # make install + +To change the default installation directory of `/usr/local`, set +`PREFIX` to something else when you call `make install`. + +license +------- +[MIT/X11][3]. + +[1]: https://lwn.net/Articles/580893/ +[2]: https://en.wikipedia.org/wiki/Setuid +[3]: https://github.com/ausbin/nsdo/blob/master/LICENSE +[4]: https://aur.archlinux.org/packages/nsdo-git/ + +manpage +------- + + nsdo(1) General Commands Manual nsdo(1) + + + + NAME + nsdo - run a command in a network namespace + + SYNOPSIS + nsdo namespace command [args ...] + + DESCRIPTION + Execute command as the current user/group in namespace, a + Linux network namespace setup with iproute2 (see ip-netns + (8)). + + By default, iproute2 places network namespaces in + /var/run/netns/, so nsdo searces for namespaces there + (including namespace). To prevent command from easily + escaping the namespace 'jail,' nsdo will exit if the cur‐ + rent namespace exists in that directory. Consequently, you + can not nest instances of nsdo. + + SEE ALSO + ip(8), ip-netns(8), namespaces(7) + + + + 2015-08-08 nsdo(1) @@ -0,0 +1,15 @@ +.TH nsdo 1 2015-08-08 +.SH NAME +nsdo \- run a command in a network namespace +.SH SYNOPSIS +.B nsdo +.I namespace command +[\fIargs\fR ...] +.SH DESCRIPTION +Execute \fIcommand\fR as the current user/group in \fInamespace\fR, a Linux network namespace setup with \fBiproute2\fR (see \fBip-netns (8)\fR). +.PP +By default, \fBiproute2\fR places network namespaces in /var/run/netns/, so \fBnsdo\fR searces for namespaces there (including \fInamespace\fR). +To prevent \fIcommand\fR from easily escaping the namespace 'jail,' \fBnsdo\fR will exit if the current namespace exists in that directory. +Consequently, you can not nest instances of \fBnsdo\fR. +.SH SEE ALSO +.B ip(8), ip-netns(8), namespaces(7) @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2015 Austin Adams + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define _GNU_SOURCE + +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <errno.h> +#include <dirent.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#define NS_PATH "/var/run/netns" +#define PROGRAM "nsdo" + +enum { + EXIT_BAD_INVOCATION = 1, + EXIT_ALREADY_IN_NAMESPACE, + EXIT_COULDNT_SETGUID, + EXIT_BAD_NETNS, + EXIT_FAILED_EXEC +}; + +enum { + ARG_PROGRAM, + ARG_NETNS, + ARG_CMD, + ARG_MIN_ARGS +}; + +void print_usage() { + fprintf(stderr, "usage: " PROGRAM " <namespace> <command> [args...]\n"); +} + +int current_ns_inode(ino_t *inode) { + struct stat nsstat; + + if (stat("/proc/self/ns/net", &nsstat) == -1) { + perror(PROGRAM ": stat(\"/proc/sys/ns/net\")"); + return 0; + } + + *inode = nsstat.st_ino; + return 1; +} + +/* return values: + 0: inode is not a namespace in the nspath + 1: it is + -1: error encountered + */ +int inode_in_nspath(ino_t inode) { + DIR *nsdir; + char *nspath; + struct dirent *ns; + struct stat nsstat; + + if ((nsdir = opendir(NS_PATH)) == NULL) { + perror(PROGRAM ": opendir(\"" NS_PATH "\")"); + return -1; + } + + while ((ns = readdir(nsdir)) != NULL) { + if (strcmp(".", ns->d_name) == 0 || strcmp("..", ns->d_name) == 0) + continue; + + if (asprintf(&nspath, "%s/%s", NS_PATH, ns->d_name) == -1) { + perror(PROGRAM ": asprintf"); + return -1; + } + + if (stat(nspath, &nsstat) == -1) { + perror(PROGRAM ": stat"); + return -1; + } + + free(nspath); + + if (nsstat.st_ino == inode) + return 1; + } + + if (errno != 0) { + perror(PROGRAM ": readdir"); + return -1; + } + + closedir(nsdir); + + return 0; +} + +int already_in_namespace() { + int status; + ino_t inode; + + if (!current_ns_inode(&inode)) + return 1; + + if ((status = inode_in_nspath(inode)) == 0) { + return 0; + } else { + if (status == 1) + fprintf(stderr, PROGRAM ": oops! i can run only in network namespaces not found in " NS_PATH "\n"); + + return 1; + } +} + +int bad_nsname(char *ns) { + return strcmp("..", ns) == 0 || strcmp(".", ns) == 0 || strchr(ns, '/') != NULL; +} + +int set_netns(char *ns) { + int nsfd; + char *nspath; + + if (bad_nsname(ns)) { + fprintf(stderr, PROGRAM ": namespace names may not contain '/' or be '.', '..'\n"); + return 0; + } + + if (asprintf(&nspath, "%s/%s", NS_PATH, ns) == -1) { + perror(PROGRAM ": asprintf"); + return 0; + } + + if ((nsfd = open(nspath, O_RDONLY)) == -1) { + perror(PROGRAM ": open"); + return 0; + } + + free(nspath); + + if (setns(nsfd, CLONE_NEWNET) == -1) { + perror(PROGRAM ": setns"); + return 0; + } else { + return 1; + } +} + +/* set euid+egid to the real uid+gid */ +int deescalate() { + if (setuid(getuid()) == -1 || setgid(getgid()) == -1) { + perror(PROGRAM ": set[gu]id"); + return 0; + } else { + return 1; + } +} + +int run(char *cmd, char **argv) { + if (execvp(cmd, argv) == -1) { + perror(PROGRAM ": execvp"); + return 0; + } else { + return 1; + } +} + +int main(int argc, char **argv) { + if (argc < ARG_MIN_ARGS) { + print_usage(); + return EXIT_BAD_INVOCATION; + } + + if (already_in_namespace()) + return EXIT_ALREADY_IN_NAMESPACE; + + if (!set_netns(argv[ARG_NETNS])) + return EXIT_BAD_NETNS; + + if (!deescalate()) + return EXIT_COULDNT_SETGUID; + + if (!run(argv[ARG_CMD], argv+ARG_CMD)) + return EXIT_FAILED_EXEC; +} diff --git a/readme.head b/readme.head new file mode 100644 index 0000000..06d959f --- /dev/null +++ b/readme.head @@ -0,0 +1,43 @@ +nsdo +==== + +This is a simple C program that runs a command inside a given [Linux +network namespace][1]. + +Effectively, it simplifies: + + $ sudo ip netns exec myns sudo -u "$USER" myprogram + +to + + $ nsdo myns myprogram + +Thanks to magic of the [setuid bit][2], it initially has root +privileges, which allows it to change its own network namespace, +`setuid()` to the user who ran the command, and then `exec()` the +requested command. + +installation +------------ + +If you're on Arch, you can build [my AUR package][4]. + +Otherwise: + + $ make + # make install + +To change the default installation directory of `/usr/local`, set +`PREFIX` to something else when you call `make install`. + +license +------- +[MIT/X11][3]. + +[1]: https://lwn.net/Articles/580893/ +[2]: https://en.wikipedia.org/wiki/Setuid +[3]: https://github.com/ausbin/nsdo/blob/master/LICENSE +[4]: https://aur.archlinux.org/packages/nsdo-git/ + +manpage +------- |