[libvirt] [PATCH] virt-login-shell joins users into lxc container.

dwalsh at redhat.com dwalsh at redhat.com
Thu Jul 18 16:18:31 UTC 2013


From: Dan Walsh <dwalsh at redhat.com>

Openshift wants to have their gears stuck into a container when they login
to the system.  virt-login-shell will join a running gear with the username of
the person running it, or attempt to start the container if it is not running.
(Currently containers do not exist if they are not running, so I can not test
this feature. But the code is there).

This tool needs to be setuid since joining a container (nsjoin) requires privs.
As root you should be able to join arbitrary containers, but when this tool is
run by a normal user it will only join the "users" container.
---
 .gitignore                 |   1 +
 po/POTFILES.in             |   1 +
 tools/Makefile.am          |  34 ++++-
 tools/login_shell.conf     |  12 ++
 tools/virt-login-shell.c   | 340 +++++++++++++++++++++++++++++++++++++++++++++
 tools/virt-login-shell.pod |  53 +++++++
 6 files changed, 440 insertions(+), 1 deletion(-)
 create mode 100755 tools/login_shell.conf
 create mode 100644 tools/virt-login-shell.c
 create mode 100644 tools/virt-login-shell.pod

diff --git a/.gitignore b/.gitignore
index 3efc2e4..dcb57bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -212,6 +212,7 @@
 /tools/libvirt-guests.init
 /tools/libvirt-guests.service
 /tools/libvirt-guests.sh
+/tools/virt-login-shell
 /tools/virsh
 /tools/virsh-*-edit.c
 /tools/virt-*-validate
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1fd84af..884b70a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -231,3 +231,4 @@ tools/virt-host-validate-common.c
 tools/virt-host-validate-lxc.c
 tools/virt-host-validate-qemu.c
 tools/virt-host-validate.c
+tools/virt-login-shell.c
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 644a86d..2f9fc86 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -37,6 +37,7 @@ EXTRA_DIST = \
 	virt-pki-validate.in				\
 	virt-sanlock-cleanup.in				\
 	virt-sanlock-cleanup.8				\
+	virt-login-shell.pod				\
 	virsh.pod					\
 	libvirt-guests.sysconf				\
 	virsh-edit.c					\
@@ -52,8 +53,11 @@ EXTRA_DIST = \
 
 DISTCLEANFILES =
 
+confdir = $(sysconfdir)/libvirt
+conf_DATA = login_shell.conf
+
 bin_SCRIPTS = virt-xml-validate virt-pki-validate
-bin_PROGRAMS = virsh virt-host-validate
+bin_PROGRAMS = virsh virt-host-validate virt-login-shell
 libexec_SCRIPTS = libvirt-guests.sh
 
 if WITH_SANLOCK
@@ -65,6 +69,7 @@ dist_man1_MANS = \
 		virt-host-validate.1 \
 		virt-pki-validate.1 \
 		virt-xml-validate.1 \
+		virt-login-shell.1 \
 		virsh.1
 if WITH_SANLOCK
 dist_man8_MANS = virt-sanlock-cleanup.8
@@ -128,6 +133,28 @@ virt_host_validate_CFLAGS = \
 		$(COVERAGE_CFLAGS)				\
 		$(NULL)
 
+virt_login_shell_SOURCES =					\
+		login-shell.conf				\
+		virt-login-shell.c
+
+virt_login_shell_LDFLAGS = $(COVERAGE_LDFLAGS)
+virt_login_shell_LDADD =					\
+		$(STATIC_BINARIES)				\
+		$(PIE_LDFLAGS)					\
+		$(RELRO_LDFLAGS) \
+		../src/libvirt.la				\
+		../src/libvirt-lxc.la				\
+		../src/libvirt-qemu.la				\
+		../gnulib/lib/libgnu.la				\
+		$(LIBXML_LIBS)
+
+virt_login_shell_CFLAGS =					\
+		$(WARN_CFLAGS)					\
+		$(PIE_CFLAGS)					\
+		$(COVERAGE_CFLAGS)				\
+		$(LIBXML_CFLAGS)				\
+		$(READLINE_CFLAGS)
+
 virsh_SOURCES =							\
 		console.c console.h				\
 		virsh.c virsh.h					\
@@ -189,6 +216,11 @@ virsh_win_icon.$(OBJEXT): virsh_win_icon.rc
 	  --output-format coff --output $@
 endif
 
+virt-login-shell.1: virt-login-shell.pod $(top_srcdir)/configure.ac
+	$(AM_V_GEN)$(POD2MAN) $< $(srcdir)/$@ \
+	    && if grep 'POD ERROR' $(srcdir)/$@ ; then \
+		rm $(srcdir)/$@; exit 1; fi
+
 virsh.1: virsh.pod $(top_srcdir)/configure.ac
 	$(AM_V_GEN)$(POD2MAN) $< $(srcdir)/$@ \
 	    && if grep 'POD ERROR' $(srcdir)/$@ ; then \
diff --git a/tools/login_shell.conf b/tools/login_shell.conf
new file mode 100755
index 0000000..23fee1a
--- /dev/null
+++ b/tools/login_shell.conf
@@ -0,0 +1,12 @@
+# Master configuration file for the virt-login-shell program.
+# All settings described here are optional - if omitted, sensible
+# defaults are used.
+
+# By default, virt-login-shell will connect you to a container running
+# with the /bin/sh program.  Modify the shell variable if you want your
+# users to run a different shell or a setup containe when joining a
+# container.  Shell commands must be a list of commands/options separated by
+# comma and delimited by square brackets. For example:
+# shell = [ "/bin/sh",  "--login" ]
+#
+shell=[ "/bin/sh",  "--login" ]
diff --git a/tools/virt-login-shell.c b/tools/virt-login-shell.c
new file mode 100644
index 0000000..f2ab9ec
--- /dev/null
+++ b/tools/virt-login-shell.c
@@ -0,0 +1,340 @@
+/*
+ * virt-login-shell.c: a shell to connect to a container
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Daniel Walsh <dwalsh at redhat.com>
+ */
+#include <config.h>
+#include "conf/domain_conf.h"
+#include "virkeyfile.h"
+#include "virconf.h"
+#include "virfile.h"
+
+#include <stdarg.h>
+#include <libvirt/libvirt.h>
+#define VIR_FROM_THIS VIR_FROM_NONE
+#include "configmake.h"
+#include "internal.h"
+#include "virerror.h"
+#include "virstring.h"
+#include "viralloc.h"
+#include "vircommand.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <selinux/selinux.h>
+
+#include <sys/wait.h>
+#include <cap-ng.h>
+
+static int nfdlist = 0;
+static int *fdlist = NULL;
+
+static void fini(virConnectPtr conn, virDomainPtr  dom) {
+    int i;
+    if (nfdlist > 0) {
+        for (i=0; i < nfdlist; i++)
+            VIR_FORCE_CLOSE(fdlist[i]);
+        VIR_FREE(fdlist);
+        nfdlist = 0;
+    }
+    if (conn)
+        virConnectClose(conn);
+    if (dom)
+        virDomainFree(dom);
+}
+
+static capng_select_t cap_set = CAPNG_SELECT_CAPS;
+
+/**
+ * This function will drop all capabilities.
+ */
+static int drop_caps(struct passwd *pw)
+{
+
+    if (capng_have_capabilities(cap_set) == CAPNG_NONE)
+        return 0;
+    capng_setpid(getpid());
+    capng_clear(cap_set);
+    if (virSetUIDGID(pw->pw_uid, pw->pw_gid) < 0)
+        return -1;
+    return capng_apply(CAPNG_SELECT_CAPS);
+}
+
+static int reset_environment(struct passwd *pw)
+{
+    char *lang_env = getenv("LANG");
+    char *lang=NULL;
+    int ret = 0;
+
+    if (lang_env) {
+        if (!VIR_STRDUP(lang_env, lang)) {
+            virReportOOMError();
+            return -1;
+        }
+    }
+
+    if (clearenv() < 0) {
+        fprintf(stderr, _("Unable to clear environment: %m"));
+        goto out;
+    }
+
+    ret |= setenv("HOME", pw->pw_dir, 1);
+    ret |= setenv("USER", pw->pw_name, 1);
+    ret |= setenv("PATH", ":/bin:/usr/bin", 1);
+    if (lang) {
+        ret |= setenv("LANG", lang, 1);
+        VIR_FREE(lang);
+    }
+out:
+    VIR_FREE(lang);
+    if (ret) {
+        fprintf(stderr, _("Unable to set environment: %m"));
+        return ret;
+    }
+    return ret;
+}
+
+static char **get_shell_argv(void) {
+    int i;
+    char** shargv;
+    virConfPtr conf = NULL;
+    virConfValuePtr p;
+    const char *login_shell_path = SYSCONFDIR "/libvirt/login_shell.conf";
+    if (!(conf = virConfReadFile(login_shell_path, 0))) {
+        fprintf(stderr, _("Failed to read %s: %m\n"), login_shell_path);
+        return NULL;
+    }
+    p = virConfGetValue(conf, "shell");
+    if (!p) {
+        fprintf(stderr, _("Failed to read shell from %s: %m\n"), login_shell_path);
+        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;
+            }
+        }
+
+        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;
+    }
+    goto cleanup;
+err:
+    for (i = 0; shargv[i]; i++)
+        VIR_FREE(shargv[i]);
+    VIR_FREE(shargv);
+    shargv=NULL;
+cleanup:
+    virConfFree(conf);
+    return shargv;
+}
+
+int
+main(int argc, char **argv) {
+    int i;
+    struct passwd *pw = NULL;
+    struct passwd pwbuf;
+    pid_t cpid;
+    pid_t ccpid;
+    long val = sysconf(_SC_GETPW_R_SIZE_MAX);
+    size_t strbuflen = val;
+    char *strbuf = NULL;
+    int ret = EXIT_FAILURE;
+    int status;
+    int status2;
+    uid_t uid = getuid();
+    int optOffset = 1;
+    char** shargv;
+    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 (!(shargv = get_shell_argv()))
+        return EXIT_FAILURE;
+
+    /* sysconf is a hint; if it fails, fall back to a reasonable size */
+    if (val < 0)
+        strbuflen = 1024;
+
+    if (VIR_ALLOC_N(strbuf, strbuflen) < 0) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    if (argc > 1) {
+        if (uid != 0) {
+            fprintf(stderr, _("Root is required to select a container to join: %m\n"));
+            return EXIT_FAILURE;
+        }
+
+        while (getpwnam_r(argv[1], &pwbuf, strbuf, strbuflen, &pw) == ERANGE) {
+            if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) {
+                virReportOOMError();
+                goto cleanup;
+            }
+        }
+
+        optOffset++;
+    } else {
+        while ((getpwuid_r(uid, &pwbuf, strbuf, strbuflen,
+                           &pw)) == ERANGE) {
+            if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) {
+                virReportOOMError();
+                goto cleanup;
+            }
+        }
+    }
+    if (!pw) {
+        fprintf(stderr, _("Passwd record does not exist: %m\n"));
+        return EXIT_FAILURE;
+    }
+
+    if ((uid == 0) && (reset_environment(pw)))
+        return EXIT_FAILURE;
+
+    conn = virConnectOpen("lxc:///");
+    if (!conn) {
+        fprintf(stderr, _("Unable to connect to lxc:///: %m\n"));
+        return EXIT_FAILURE;
+    }
+
+    dom = virDomainLookupByName(conn, pw->pw_name);
+    if (!dom) {
+        fprintf(stderr, _("Container %s does not exist: %m\n"), pw->pw_name);
+        return EXIT_FAILURE;
+    }
+
+    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"), pw->pw_name, virGetLastErrorMessage());
+            return EXIT_FAILURE;
+        }
+    }
+
+    if ((nfdlist = virDomainLxcOpenNamespace(dom, &fdlist, 0)) < 0) {
+        fprintf(stderr,_("Can't open %s namespace: %m\n"), pw->pw_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) {
+        /* Fork once because we don't want to affect
+         * virt-login-shell's namespace itself
+         */
+        if (virSetUIDGID(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;
+        }
+
+        drop_caps(pw);
+
+        if (virFork(&ccpid) < 0)
+            return EXIT_FAILURE;
+
+        if (ccpid == 0) {
+            if (chdir(pw->pw_dir) < 0) {
+                fprintf(stderr, _("Unable chdir(%s): %m\n"), pw->pw_dir);
+                return EXIT_FAILURE;
+            }
+            if (execv(shargv[0], (char *const*) shargv) < 0) {
+                fprintf(stderr, _("Unable exec shell %s: %m\n"), shargv[0]);
+                return EXIT_FAILURE;
+            }
+        }
+        wait(&status2);
+        return EXIT_SUCCESS;
+    }
+    wait(&status);
+    ret = EXIT_SUCCESS;
+    goto cleanup;
+err:
+    ret = EXIT_FAILURE;
+cleanup:
+    fini(conn, dom);
+    for (i = 0; shargv[i]; i++)
+        VIR_FREE(shargv[i]);
+    VIR_FREE(shargv);
+    VIR_FREE(strbuf);
+    VIR_FREE(seclabel);
+    VIR_FREE(secmodel);
+    return ret;
+}
diff --git a/tools/virt-login-shell.pod b/tools/virt-login-shell.pod
new file mode 100644
index 0000000..f32eed0
--- /dev/null
+++ b/tools/virt-login-shell.pod
@@ -0,0 +1,53 @@
+=head1 NAME
+
+virt-login-shell - tool to execute a shell within a container matching the users name
+
+=head1 SYNOPSIS
+
+B<virt-login-shell>
+
+B<virt-login-shell> USERNAME
+
+=head1 DESCRIPTION
+
+The B<virt-login-shell> program is setuid shell that is used to join
+an LXC container that matches the users name.  If the container is not
+running virt-login-shell will attempt to start the container.  If
+virt-sandbox-shell is run by root, you can specify an alternate username
+to join the container.  Normal users will get added to a container that matches their username, if it exists.
+
+The basic structure of most virsh usage is:
+
+  virt-login-shell
+
+By default, virt-login-shell will execute the /bin/sh program for the user.  Modify the /etc/libvirt/login_shell.conf file to change the default shell.
+
+=head1 BUGS
+
+Report any bugs discovered to the libvirt community via the mailing
+list C<http://libvirt.org/contact.html> or bug tracker C<http://libvirt.org/bugs.html>.
+Alternatively report bugs to your software distributor / vendor.
+
+=head1 AUTHORS
+
+  Please refer to the AUTHORS file distributed with libvirt.
+
+  Daniel Walsh <dwalsh at redhat dot com>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2013 Red Hat, Inc., and the authors listed in the
+libvirt AUTHORS file.
+
+=head1 LICENSE
+
+virt-login-shell is distributed under the terms of the GNU LGPL v2+.
+This is free software; see the source for copying conditions. There
+is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE
+
+=head1 SEE ALSO
+
+L<virsh(1)>, L<http://www.libvirt.org/>
+
+=cut
-- 
1.8.3.1




More information about the libvir-list mailing list