[libvirt] [PATCH] virt-login-shell joins users into lxc container.
Daniel P. Berrange
berrange at redhat.com
Fri Jul 19 13:38:43 UTC 2013
On Thu, Jul 18, 2013 at 05:25:56PM -0400, dwalsh at redhat.com wrote:
> diff --git a/libvirt.spec.in b/libvirt.spec.in
> index e0e0004..34e3594 100644
> --- a/libvirt.spec.in
> +++ b/libvirt.spec.in
> @@ -1751,6 +1751,7 @@ fi
> %config(noreplace) %{_sysconfdir}/logrotate.d/libvirtd.qemu
> %endif
> %if %{with_lxc}
> +%config(noreplace) %{_sysconfdir}/libvirt/virt-login-shell.conf
The Makefile.am for this is not conditional on WITH_LXC, so
you shoyuld have this outside the %{with_lxc} block.
> %config(noreplace) %{_sysconfdir}/libvirt/lxc.conf
> %config(noreplace) %{_sysconfdir}/logrotate.d/libvirtd.lxc
> %endif
> @@ -1830,6 +1831,8 @@ fi
>
> %if %{with_lxc}
> %attr(0755, root, root) %{_libexecdir}/libvirt_lxc
> +%attr(4755, root, root) %{_bindir}/virt-login-shell
> +%{_mandir}/man1/virt-login-shell.1*
Same for these two.
> diff --git a/tools/Makefile.am b/tools/Makefile.am
> index 644a86d..b4bed0b 100644
> --- a/tools/Makefile.am
> +++ b/tools/Makefile.am
> +virt_login_shell_LDFLAGS = $(COVERAGE_LDFLAGS)
> +virt_login_shell_LDADD = \
> + $(STATIC_BINARIES) \
> + $(PIE_LDFLAGS) \
> + $(RELRO_LDFLAGS) \
> + ../src/libvirt.la \
> + ../src/libvirt-lxc.la \
> + ../gnulib/lib/libgnu.la \
> + $(LIBXML_LIBS)
Can remove LIBXML_LIBS
> +
> +virt_login_shell_CFLAGS = \
> + $(WARN_CFLAGS) \
> + $(PIE_CFLAGS) \
> + $(COVERAGE_CFLAGS) \
> + $(LIBXML_CFLAGS) \
> + $(READLINE_CFLAGS)
Can remove LIBXML & READLINE here.
> diff --git a/tools/virt-login-shell.c b/tools/virt-login-shell.c
> new file mode 100644
> index 0000000..2528199
> --- /dev/null
> +++ b/tools/virt-login-shell.c
> @@ -0,0 +1,310 @@
> +#include <selinux/selinux.h>
I don't think you need this, since all selinux stuff is hidden
inside libvirt APIs.
> +#define VIR_FROM_THIS VIR_FROM_NONE
> +
> +static int nfdlist = 0;
s/int/size_t/
> +static int *fdlist = NULL;
> +
> +static void virLoginShellFini(virConnectPtr conn, virDomainPtr dom) {
> + size_t i;
> + if (nfdlist > 0) {
No need to have this if (nfdlist > 0) since the next for() condition
already handles that, and VIR_FREE(NULL) is safe.
> + for (i=0; i < nfdlist; i++)
> + VIR_FORCE_CLOSE(fdlist[i]);
> + VIR_FREE(fdlist);
> + nfdlist = 0;
> + }
> + if (dom)
> + virDomainFree(dom);
> + if (conn)
> + virConnectClose(conn);
> +}
> +
> +static int virLoginShellAllowedUser(virConfPtr conf,
> + uid_t uid,
Some trailing whitespace at the end of the line - make syntax-check
will vcalidate this.
> + gid_t gid,
> + char *name) {
> + virConfValuePtr p;
> + int ret = 0;
Again, normal practice is to initialize 'ret = -1' - ie expect
failure
> + char *ptr = NULL;
> + size_t i;
> + gid_t *groups = NULL;
> +
> + p = virConfGetValue(conf, "allowed_users");
> + if (!p) {
> + errno = EPERM;
> + fprintf(stderr, _("%s is not allowed to connect a container: %m\n"), name);
> + goto err;
> + }
> + if (p && p->type == VIR_CONF_LIST) {
> + size_t len;
> + virConfValuePtr pp;
> +
> + /* Calc length and check items */
> + for (len = 0, pp = p->list; pp; len++, pp = pp->next) {
> + if (pp->type != VIR_CONF_STRING) {
> + fprintf(stderr, _("shell must be a list of strings"));
> + goto err;
> + } else {
> + if (STREQ(pp->str, "*") || STREQ(pp->str, name)) {
> + ret = 0;
> + goto cleanup;
> + }
You don't wnat to be using STREQ for this. Instead us fnmatch()
which does proper glob matching.
> + if (pp->str[0] == '%') {
> + ptr=&pp->str[1];
> + gid_t groupid;
> + if (virGetGroupID(ptr, &groupid) < 0) {
> + fprintf(stderr, _("(%s) is not a valid group\n"), ptr);
> + continue;
> + }
> + if (virGetGroupList(uid, gid, &groups) < 0)
> + goto err;
> + for (i=0; groups[i]; i++) {
> + if (groups[i] == groupid) {
> + ret = 0;
> + goto cleanup;
> + }
> + }
We want to have glob matching support on group names too, so you
can't just match on groupid. You'll need to turn the groupid into
a groupname, and then use fnmatch() again.
> + }
> + }
> + }
> + }
> + errno = EPERM;
> + fprintf(stderr, _("%s is not allowed to connect a container: %m\n"), name);
> +err:
You don't need this err: label if you initialize ret == -1;
> + ret = -1;
and change this to 'ret = 0'
> +cleanup:
> + VIR_FREE(groups);
> + return ret;
> +}
> +
> +static char **virLoginShellGetShellArgv(virConfPtr conf) {
> + size_t i;
> + char **shargv;
> + virConfValuePtr p;
> + p = virConfGetValue(conf, "shell");
> + if (!p)
> + return virStringSplit("/bin/sh --login", " ", 3);
> +
> + if (p && p->type == VIR_CONF_LIST) {
> + size_t len;
> + virConfValuePtr pp;
> +
> + /* Calc length and check items */
> + for (len = 0, pp = p->list; pp; len++, pp = pp->next) {
> + if (pp->type != VIR_CONF_STRING) {
> + fprintf(stderr, _("shell must be a list of strings"));
> + goto err;
> + }
> + }
> +
> + if (VIR_ALLOC_N(shargv, len + 1) < 0) {
> + fprintf(stderr, _("out of memory"));
> + goto err;
> + }
> + for (i = 0, pp = p->list; pp; i++, pp = pp->next) {
> + if (VIR_STRDUP(shargv[i], pp->str) < 0) {
> + fprintf(stderr, _("out of memory"));
> + goto err;
> + }
> + }
> + shargv[len] = NULL;
> + }
> + return shargv;
> +err:
s/err/error/
> + virStringFreeList(shargv);
> + return NULL;
> +}
> +
> +int
> +main(int argc, char **argv) {
> + virConfPtr conf = NULL;
> + const char *login_shell_path = SYSCONFDIR "/libvirt/virt-login-shell.conf";
> + pid_t cpid;
> + int ret = EXIT_FAILURE;
> + int status;
> + int status2;
> + uid_t uid = getuid();
> + gid_t gid = getgid();
> + char *name;
> + char **shargv = NULL;
> + virSecurityModelPtr secmodel = NULL;
> + virSecurityLabelPtr seclabel = NULL;
> + virDomainPtr dom = NULL;
> + virConnectPtr conn = NULL;
> + if (!setlocale(LC_ALL, "")) {
> + perror("setlocale");
> + /* failure to setup locale is not fatal */
> + }
> + if (!bindtextdomain(PACKAGE, LOCALEDIR)) {
> + perror("bindtextdomain");
> + return EXIT_FAILURE;
> + }
> + if (!textdomain(PACKAGE)) {
> + perror("textdomain");
> + return EXIT_FAILURE;
> + }
> +
> + if (uid == 0) {
> + errno = EPERM;
> + fprintf(stderr, _("%s must be run by non root users: %m\n"), argv[0]);
> + goto err;
> + }
> +
> + if (argc > 1) {
> + errno = EINVAL;
> + fprintf(stderr, _("%s takes no options: %m\n"), argv[0]);
> + goto err;
> + }
More trailing whitespace
> +
> + name = virGetUserName(uid);
> +
> + if (!(conf = virConfReadFile(login_shell_path, 0))) {
> + fprintf(stderr, _("Failed to read %s: %m\n"), login_shell_path);
> + goto err;
> + }
> + if (virLoginShellAllowedUser(conf, uid, gid, name) < 0)
> + goto err;
> +
> + if (!(shargv = virLoginShellGetShellArgv(conf)))
> + goto err;
> +
Trailing whitespace.
> + conn = virConnectOpen("lxc:///");
> + if (!conn) {
> + fprintf(stderr, _("Unable to connect to lxc:///: %m\n"));
> + goto err;
> + }
> +
> + dom = virDomainLookupByName(conn, name);
> + if (!dom) {
> + fprintf(stderr, _("Container %s does not exist: %m\n"), name);
> + goto err;
> + }
> +
> + if (! virDomainIsActive(dom) && virDomainCreate(dom)) {
> + virErrorPtr last_error;
> + last_error = virGetLastError();
> + if (last_error->code != VIR_ERR_OPERATION_INVALID) {
> + fprintf(stderr,_("Can't create %s container: %s\n"), name, virGetLastErrorMessage());
> + goto err;
> + }
> + }
> +
> + if ((nfdlist = virDomainLxcOpenNamespace(dom, &fdlist, 0)) < 0) {
> + fprintf(stderr,_("Can't open %s namespace: %m\n"), name);
> + goto err;
> + }
> +
> + if (VIR_ALLOC(secmodel) < 0) {
> + fprintf(stderr, _("Failed to allocate security model: %m\n"));
> + goto err;
> + }
> + if (VIR_ALLOC(seclabel) < 0) {
> + fprintf(stderr, _("Failed to allocate security label: %m\n"));
> + goto err;
> + }
> + if (virNodeGetSecurityModel(conn, secmodel) < 0)
> + goto err;
> + if (virDomainGetSecurityLabel(dom, seclabel) < 0)
> + goto err;
> +
> + if (virFork(&cpid) < 0)
> + goto err;
> +
> + if (cpid == 0) {
> + gid_t *groups = NULL;
> + int ngroups;
> + pid_t ccpid;
> +
> + /* Fork once because we don't want to affect
> + * virt-login-shell's namespace itself
> + */
> + if (virSetUIDGID(0, 0, 0, 0) < 0) {
> + fprintf(stderr, _("Unable to setresuid: %m\n"));
> + return EXIT_FAILURE;
> + }
> +
> + if (virDomainLxcEnterSecurityLabel(secmodel,
> + seclabel,
> + NULL,
> + 0) < 0)
> + return EXIT_FAILURE;
> +
> + if (nfdlist > 0) {
> + if (virDomainLxcEnterNamespace(dom,
> + nfdlist,
> + fdlist,
> + NULL,
> + NULL,
> + 0) < 0)
> + return EXIT_FAILURE;
> + }
> +
> + if ((ngroups = virGetGroupList(uid, gid, &groups)) < 0)
> + return -1;
> +
> + ret = virSetUIDGIDWithCaps(uid, gid, groups, ngroups, 0, 0);
> + VIR_FREE(groups);
> + if (ret < 0)
> + return -1;
> +
> + if (virFork(&ccpid) < 0)
> + return EXIT_FAILURE;
> +
> + if (ccpid == 0) {
> + char *homedir = virGetUserDirectory();
You'll need to call virGetUserDirectory() before any fork(), since it
calls code which is not async-signal safe.
> + if (chdir(homedir) < 0) {
> + fprintf(stderr, _("Unable chdir(%s): %m\n"), homedir);
> + return EXIT_FAILURE;
> + }
> + if (execv(shargv[0], (char *const*) shargv) < 0) {
> + fprintf(stderr, _("Unable exec shell %s: %m\n"), shargv[0]);
> + return EXIT_FAILURE;
> + }
> + }
> + return virProcessWait(ccpid, &status2);
> + }
> + ret = virProcessWait(cpid, &status);
Hmm, looking at this again, I'm wondering you need to fork()
at all. In virsh we do the double-fork dance, because virsh
is an interactive shell & we don't want to affect other parts
of virsh.
This login shell though is different - its only job is to run
inside the namespace. So can't the main process just enter
the namespace directly ?
> + goto cleanup;
> +err:
> + ret = EXIT_FAILURE;
You're pre-initializing ret to EXIT_FAILURE. So remove the
separate 'err' label and make everything jump to 'cleanup'
intead. Then just have a 'ret = EXIT_SUCCESS'
> +cleanup:
> + virConfFree(conf);
> + virLoginShellFini(conn, dom);
> + virStringFreeList(shargv);
> + VIR_FREE(seclabel);
> + VIR_FREE(secmodel);
> + return ret;
> +}
Regards,
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://autobuild.org -o- http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
More information about the libvir-list
mailing list