[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