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 <berrange@redhat.com>
This commit is contained in:
Dan Walsh 2013-08-08 16:36:31 +01:00 committed by Daniel P. Berrange
parent 6807238d87
commit 54d69f540c
7 changed files with 472 additions and 1 deletions

1
.gitignore vendored
View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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 \

350
tools/virt-login-shell.c Normal file
View File

@ -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
* <http://www.gnu.org/licenses/>.
*
* Daniel Walsh <dwalsh@redhat.com>
*/
#include <config.h>
#include <stdarg.h>
#include <getopt.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <fnmatch.h>
#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;
}

View File

@ -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 = [ "*" ]

View File

@ -0,0 +1,62 @@
=head1 NAME
virt-login-shell - tool to execute a shell within a container matching the users name
=head1 SYNOPSIS
B<virt-login-shell>
=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.
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<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