From 54d69f540c9928da98f10202b3f21b7abb00bac1 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Thu, 8 Aug 2013 16:36:31 +0100 Subject: [PATCH] Introduce a virt-login-shell binary Add a virt-login-shell binary that can be set as a user's shell, such that when they login, it causes them to enter the LXC container with a name matching their user name. Signed-off-by: Daniel P. Berrange --- .gitignore | 1 + libvirt.spec.in | 3 + po/POTFILES.in | 1 + tools/Makefile.am | 30 +++- tools/virt-login-shell.c | 350 ++++++++++++++++++++++++++++++++++++ tools/virt-login-shell.conf | 26 +++ tools/virt-login-shell.pod | 62 +++++++ 7 files changed, 472 insertions(+), 1 deletion(-) create mode 100644 tools/virt-login-shell.c create mode 100644 tools/virt-login-shell.conf create mode 100644 tools/virt-login-shell.pod diff --git a/.gitignore b/.gitignore index ae9de0b859..faf82e408d 100644 --- a/.gitignore +++ b/.gitignore @@ -218,6 +218,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/libvirt.spec.in b/libvirt.spec.in index 79c5a2cc46..713511d4d5 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -2007,14 +2007,17 @@ fi %doc AUTHORS ChangeLog.gz NEWS README COPYING COPYING.LESSER TODO %config(noreplace) %{_sysconfdir}/libvirt/libvirt.conf +%config(noreplace) %{_sysconfdir}/libvirt/virt-login-shell.conf %{_mandir}/man1/virsh.1* %{_mandir}/man1/virt-xml-validate.1* %{_mandir}/man1/virt-pki-validate.1* %{_mandir}/man1/virt-host-validate.1* +%{_mandir}/man1/virt-login-shell.1* %{_bindir}/virsh %{_bindir}/virt-xml-validate %{_bindir}/virt-pki-validate %{_bindir}/virt-host-validate +%attr(4755, root, root) %{_bindir}/virt-login-shell %{_libdir}/lib*.so.* %if %{with_dtrace} diff --git a/po/POTFILES.in b/po/POTFILES.in index 24b32dbe0d..36d027a5c6 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -232,3 +232,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 644a86d935..00c582af88 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 = virt-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,24 @@ virt_host_validate_CFLAGS = \ $(COVERAGE_CFLAGS) \ $(NULL) +virt_login_shell_SOURCES = \ + virt-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 \ + ../gnulib/lib/libgnu.la + +virt_login_shell_CFLAGS = \ + $(WARN_CFLAGS) \ + $(PIE_CFLAGS) \ + $(COVERAGE_CFLAGS) + virsh_SOURCES = \ console.c console.h \ virsh.c virsh.h \ @@ -189,6 +212,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/virt-login-shell.c b/tools/virt-login-shell.c new file mode 100644 index 0000000000..ffbc713454 --- /dev/null +++ b/tools/virt-login-shell.c @@ -0,0 +1,350 @@ +/* + * 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 + * . + * + * Daniel Walsh + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "virerror.h" +#include "virconf.h" +#include "virutil.h" +#include "virfile.h" +#include "virprocess.h" +#include "configmake.h" +#include "virstring.h" +#include "viralloc.h" +#include "vircommand.h" +#define VIR_FROM_THIS VIR_FROM_NONE + +static ssize_t nfdlist = 0; +static int *fdlist = NULL; +static const char *conf_file = SYSCONFDIR "/libvirt/virt-login-shell.conf"; + +static void virLoginShellFini(virConnectPtr conn, virDomainPtr dom) +{ + size_t i; + + 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, + const char *name, + gid_t *groups) +{ + virConfValuePtr p; + int ret = -1; + char *ptr = NULL; + size_t i; + char *gname = NULL; + + p = virConfGetValue(conf, "allowed_users"); + if (p && p->type == VIR_CONF_LIST) { + virConfValuePtr pp; + + /* Calc length and check items */ + for (pp = p->list; pp; pp = pp->next) { + if (pp->type != VIR_CONF_STRING) { + virReportSystemError(EINVAL, "%s", _("shell must be a list of strings")); + goto cleanup; + } else { + /* + If string begins with a % this indicates a linux group. + Check to see if the user is in the Linux Group. + */ + if (pp->str[0] == '%') { + ptr = &pp->str[1]; + if (!ptr) + continue; + for (i = 0; groups[i]; i++) { + if (!(gname = virGetGroupName(groups[i]))) + continue; + if (fnmatch(ptr, gname, 0) == 0) { + ret = 0; + goto cleanup; + } + VIR_FREE(gname); + } + VIR_FREE(groups); + continue; + } + if (fnmatch(pp->str, name, 0) == 0) { + ret = 0; + goto cleanup; + } + } + } + } + virReportSystemError(EPERM, _("%s not listed as an allowed_users in %s"), name, conf_file); +cleanup: + VIR_FREE(gname); + VIR_FREE(groups); + return ret; +} + +static char **virLoginShellGetShellArgv(virConfPtr conf) +{ + size_t i; + char **shargv=NULL; + virConfValuePtr p; + + p = virConfGetValue(conf, "shell"); + if (!p) + return virStringSplit("/bin/sh -l", " ", 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) { + virReportSystemError(EINVAL, "%s", _("shell must be a list of strings")); + goto error; + } + } + + if (VIR_ALLOC_N(shargv, len + 1) < 0) + goto error; + for (i = 0, pp = p->list; pp; i++, pp = pp->next) { + if (VIR_STRDUP(shargv[i], pp->str) < 0) + goto error; + } + shargv[len] = NULL; + } + return shargv; +error: + virStringFreeList(shargv); + return NULL; +} + +static char *progname; + +/* + * Print usage + */ +static void +usage(void) +{ + fprintf(stdout, _("\n" + "%s is a privileged program that allows non root users \n" + "specified in %s to join a Linux container \n" + "with a matching user name and launch a shell. \n" + "\n%s [options]\n\n" + " options:\n" + " -h | --help this help:\n\n"), progname, conf_file, progname); + return; +} + +int +main(int argc, char **argv) +{ + virConfPtr conf = NULL; + const char *login_shell_path = conf_file; + pid_t cpid; + int ret = EXIT_FAILURE; + int status; + int status2; + uid_t uid = getuid(); + gid_t gid = getgid(); + char *name = NULL; + char **shargv = NULL; + virSecurityModelPtr secmodel = NULL; + virSecurityLabelPtr seclabel = NULL; + virDomainPtr dom = NULL; + virConnectPtr conn = NULL; + char *homedir = NULL; + int arg; + int longindex = -1; + int ngroups; + gid_t *groups = NULL; + + struct option opt[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + if (virInitialize() < 0) { + fprintf(stderr, _("Failed to initialize libvirt Error Handling")); + return EXIT_FAILURE; + } + + virSetErrorFunc(NULL, NULL); + virSetErrorLogPriorityFunc(NULL); + + progname = argv[0]; + if (!setlocale(LC_ALL, "")) { + perror("setlocale"); + /* failure to setup locale is not fatal */ + } + if (!bindtextdomain(PACKAGE, LOCALEDIR)) { + perror("bindtextdomain"); + return ret; + } + if (!textdomain(PACKAGE)) { + perror("textdomain"); + return ret; + } + + /* The only option we support is help + */ + while ((arg = getopt_long(argc, argv, "h", opt, &longindex)) != -1) { + switch (arg) { + case 'h': + usage(); + exit(EXIT_SUCCESS); + break; + } + } + + if (argc > optind) { + virReportSystemError(EINVAL, _("%s takes no options"), progname); + errno = EINVAL; + goto cleanup; + } + + if (uid == 0) { + virReportSystemError(EPERM, _("%s must be run by non root users"), progname); + goto cleanup; + } + + name = virGetUserName(uid); + if (!name) + goto cleanup; + + homedir = virGetUserDirectoryByUID(uid); + if (!homedir) + goto cleanup; + + if (!(conf = virConfReadFile(login_shell_path, 0))) + goto cleanup; + + if ((ngroups = virGetGroupList(uid, gid, &groups)) < 0) + goto cleanup; + + if (virLoginShellAllowedUser(conf, name, groups) < 0) + goto cleanup; + + if (!(shargv = virLoginShellGetShellArgv(conf))) + goto cleanup; + + conn = virConnectOpen("lxc:///"); + if (!conn) + goto cleanup; + + dom = virDomainLookupByName(conn, name); + if (!dom) + goto cleanup; + + if (!virDomainIsActive(dom) && virDomainCreate(dom)) { + virErrorPtr last_error; + last_error = virGetLastError(); + if (last_error->code != VIR_ERR_OPERATION_INVALID) { + virReportSystemError(last_error->code,_("Can't create %s container: %s"), name, virGetLastErrorMessage()); + goto cleanup; + } + } + + if ((nfdlist = virDomainLxcOpenNamespace(dom, &fdlist, 0)) < 0) + goto cleanup; + if (VIR_ALLOC(secmodel) < 0) + goto cleanup; + if (VIR_ALLOC(seclabel) < 0) + goto cleanup; + if (virNodeGetSecurityModel(conn, secmodel) < 0) + goto cleanup; + if (virDomainGetSecurityLabel(dom, seclabel) < 0) + goto cleanup; + + if (virFork(&cpid) < 0) + goto cleanup; + + if (cpid == 0) { + pid_t ccpid; + + /* Fork once because we don't want to affect + * virt-login-shell's namespace itself + */ + if (virSetUIDGID(0, 0, NULL, 0) < 0) + 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; + } + + ret = virSetUIDGID(uid, gid, groups, ngroups); + VIR_FREE(groups); + if (ret < 0) + return EXIT_FAILURE; + + if (virFork(&ccpid) < 0) + return EXIT_FAILURE; + + if (ccpid == 0) { + if (chdir(homedir) < 0) { + virReportSystemError(errno, _("Unable chdir(%s)"), homedir); + return EXIT_FAILURE; + } + if (execv(shargv[0], (char *const*) shargv) < 0) { + virReportSystemError(errno, _("Unable exec shell %s"), shargv[0]); + return -errno; + } + } + return virProcessWait(ccpid, &status2); + } + ret = virProcessWait(cpid, &status); + +cleanup: + virConfFree(conf); + virLoginShellFini(conn, dom); + virStringFreeList(shargv); + VIR_FREE(name); + VIR_FREE(homedir); + VIR_FREE(seclabel); + VIR_FREE(secmodel); + VIR_FREE(groups); + if (ret) + virDispatchError(NULL); + return ret; +} diff --git a/tools/virt-login-shell.conf b/tools/virt-login-shell.conf new file mode 100644 index 0000000000..835fd3fecd --- /dev/null +++ b/tools/virt-login-shell.conf @@ -0,0 +1,26 @@ +# 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 container when joining a +# container. Shell commands must be a list of commands/options separated by +# comma and delimited by square brackets. Defaults to: /bin/sh -l. +# Modify and uncomment the following to modify the login shell. +# shell = [ "/bin/sh", "-l" ] + +# allowed_users specifies the user names of all users that are allowed to +# execute virt-login-shell. You can specify the users as a comma +# separated list of usernames or user groups. +# The list of names support glob syntax. +# To disallow all users (default) +# allowed_users = [] +# If you do not specify any names (default) then no one is allowed +# to use this executable. +# To allow fred and joe only +# allowed_users = ["fred", "joe"] +# To allow all users within a specific group prefix the group name with %. +# allowed_users = ["%engineers"] +# To allow all users specify the following +# allowed_users = [ "*" ] diff --git a/tools/virt-login-shell.pod b/tools/virt-login-shell.pod new file mode 100644 index 0000000000..0cd35cf6fd --- /dev/null +++ b/tools/virt-login-shell.pod @@ -0,0 +1,62 @@ +=head1 NAME + +virt-login-shell - tool to execute a shell within a container matching the users name + +=head1 SYNOPSIS + +B + +=head1 DESCRIPTION + +The B 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. +virt-sandbox-shell is not allowed to be run by root. Normal users will get +added to a container that matches their username, if it exists. And they are +configured in /etc/libvirt/virt-login-shell.conf. + +The basic structure of most virt-login-shell usage is: + + virt-login-shell + +=head1 CONFIG + +By default, virt-login-shell will execute the /bin/sh program for the user. +You can modify this behaviour by defining the shell variable in /etc/libvirt/virt-login-shell.conf. + +eg. shell = [ "/bin/ksh", "--login"] + +By default no users are allowed to user virt-login-shell, if you want to allow +certain users to use virt-login-shell, you need to modify the allowed_users variable in /etc/libvirt/virt-login-shell.conf. + +eg. allowed_users = [ "tom", "dick", "harry" ] + +=head1 BUGS + +Report any bugs discovered to the libvirt community via the mailing +list C or bug tracker C. +Alternatively report bugs to your software distributor / vendor. + +=head1 AUTHORS + + Please refer to the AUTHORS file distributed with libvirt. + + Daniel Walsh + +=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, L + +=cut