Commit 982f3f59 authored by Leigh Stoller's avatar Leigh Stoller

Use sunlink flag to prevent users from removing critical directories in

/proj. Applied to top level only for now, since that was reasonably easy
to do, since projects and group stuff is all done on ops already (where
the chflags has to run). We could apply this to experiment and image
directories too, but we all know the better approach is to stop mounting
/proj on experimental nodes, right?

Also a new script mkprojdirs to create/recreate missing project
directories and do the chflags (calls over to ops and uses the
existing proxy script).
parent cec5c266
...@@ -88,6 +88,7 @@ my $CHPASS = "/usr/bin/chpass"; ...@@ -88,6 +88,7 @@ my $CHPASS = "/usr/bin/chpass";
my $CHOWN = "/usr/sbin/chown"; my $CHOWN = "/usr/sbin/chown";
my $CHMOD = "/bin/chmod"; my $CHMOD = "/bin/chmod";
my $MKDIR = "/bin/mkdir"; my $MKDIR = "/bin/mkdir";
my $CHFLAGS = "/bin/chflags";
my $NOLOGIN = "/sbin/nologin"; my $NOLOGIN = "/sbin/nologin";
my $MV = "/bin/mv"; my $MV = "/bin/mv";
my $ZFS = "/sbin/zfs"; my $ZFS = "/sbin/zfs";
...@@ -95,11 +96,19 @@ my $KEYGEN = "/usr/bin/ssh-keygen"; ...@@ -95,11 +96,19 @@ my $KEYGEN = "/usr/bin/ssh-keygen";
my $SKEL = "/usr/share/skel"; my $SKEL = "/usr/share/skel";
my $PIDFILE = "/var/run/mountd.pid"; my $PIDFILE = "/var/run/mountd.pid";
my $TSFILE = "/var/run/mountd.ts"; my $TSFILE = "/var/run/mountd.ts";
my $USEFLAGS = 0;
# XXX # XXX
my $NOSUCHUSER = 67; my $NOSUCHUSER = 67;
my $USEREXISTS = 65; my $USEREXISTS = 65;
# We use flags to prevent deletion of certain dirs, on FreeBSD 10 or greater.
if (`uname -r` =~ /^(\d+)\.(\d+)/) {
if ($1 >= 10) {
$USEFLAGS = 1;
}
}
# #
# Testbed Support libraries # Testbed Support libraries
# #
...@@ -148,6 +157,8 @@ sub MakeDir($$); ...@@ -148,6 +157,8 @@ sub MakeDir($$);
sub WhackDir($$); sub WhackDir($$);
sub mysystem($); sub mysystem($);
sub runBusyLoop($); sub runBusyLoop($);
sub SetNoDelete($);
sub ClearNoDelete($);
# #
# Check args. # Check args.
...@@ -462,7 +473,7 @@ sub AddProject() ...@@ -462,7 +473,7 @@ sub AddProject()
my $unix_uid = shift(@ARGV); my $unix_uid = shift(@ARGV);
# Create the project unix group # Create the project unix group
if (mysystem("egrep -q -s '^${unix_name}:' /etc/group")) { if (system("egrep -q -s '^${unix_name}:' /etc/group")) {
print "Adding group $unix_name ...\n"; print "Adding group $unix_name ...\n";
if (runBusyLoop("$GROUPADD $unix_name -g $unix_gid")) { if (runBusyLoop("$GROUPADD $unix_name -g $unix_gid")) {
...@@ -481,6 +492,9 @@ sub AddProject() ...@@ -481,6 +492,9 @@ sub AddProject()
if (! chown($unix_uid, $unix_gid, "$path")) { if (! chown($unix_uid, $unix_gid, "$path")) {
fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!"); fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!");
} }
if (SetNoDelete($path)) {
fatal("Could not set no delete on '$path'!\n");
}
# Create required /proj subdirs # Create required /proj subdirs
foreach my $dir (@DIRLIST) { foreach my $dir (@DIRLIST) {
...@@ -494,6 +508,9 @@ sub AddProject() ...@@ -494,6 +508,9 @@ sub AddProject()
if (! chown($unix_uid, $unix_gid, "$path")) { if (! chown($unix_uid, $unix_gid, "$path")) {
fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!"); fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!");
} }
if (SetNoDelete($path)) {
fatal("Could not set no delete on '$path'!\n");
}
} }
# Create the /groups directory # Create the /groups directory
...@@ -507,6 +524,9 @@ sub AddProject() ...@@ -507,6 +524,9 @@ sub AddProject()
if (! chown($unix_uid, $unix_gid, "$path")) { if (! chown($unix_uid, $unix_gid, "$path")) {
fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!"); fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!");
} }
if (SetNoDelete($path)) {
fatal("Could not set no delete on '$path'!\n");
}
# Create a symlink for the default group # Create a symlink for the default group
$path = "$GROUPROOT/$name/$name"; $path = "$GROUPROOT/$name/$name";
...@@ -515,6 +535,9 @@ sub AddProject() ...@@ -515,6 +535,9 @@ sub AddProject()
fatal("Could not symlink $PROJROOT/$name to $path"); fatal("Could not symlink $PROJROOT/$name to $path");
} }
} }
if (SetNoDelete($path)) {
fatal("Could not set no delete on '$path'!\n");
}
# Finally, create /scratch dir if supported # Finally, create /scratch dir if supported
if ($SCRATCHROOT) { if ($SCRATCHROOT) {
...@@ -528,6 +551,9 @@ sub AddProject() ...@@ -528,6 +551,9 @@ sub AddProject()
if (! chown($unix_uid, $unix_gid, "$path")) { if (! chown($unix_uid, $unix_gid, "$path")) {
fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!"); fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!");
} }
if (SetNoDelete($path)) {
fatal("Could not set no delete on '$path'!\n");
}
} }
return 0; return 0;
...@@ -548,7 +574,7 @@ sub AddGroup() ...@@ -548,7 +574,7 @@ sub AddGroup()
my $projname = shift(@ARGV); my $projname = shift(@ARGV);
# Create the group unix group # Create the group unix group
if (mysystem("egrep -q -s '^${unix_name}:' /etc/group")) { if (system("egrep -q -s '^${unix_name}:' /etc/group")) {
print "Adding group $unix_name ...\n"; print "Adding group $unix_name ...\n";
if (runBusyLoop("$GROUPADD $unix_name -g $unix_gid")) { if (runBusyLoop("$GROUPADD $unix_name -g $unix_gid")) {
...@@ -568,6 +594,9 @@ sub AddGroup() ...@@ -568,6 +594,9 @@ sub AddGroup()
if (! chown($unix_uid, $unix_gid, "$path")) { if (! chown($unix_uid, $unix_gid, "$path")) {
fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!"); fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!");
} }
if (SetNoDelete($path)) {
fatal("Could not set no delete on '$path'!\n");
}
# Create required /groups/gid subdirs # Create required /groups/gid subdirs
foreach my $dir (@GDIRLIST) { foreach my $dir (@GDIRLIST) {
...@@ -581,6 +610,9 @@ sub AddGroup() ...@@ -581,6 +610,9 @@ sub AddGroup()
if (! chown($unix_uid, $unix_gid, "$path")) { if (! chown($unix_uid, $unix_gid, "$path")) {
fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!"); fatal("Could not chown '$path' to $unix_uid/$unix_gid: $!");
} }
if (SetNoDelete($path)) {
fatal("Could not set no delete on '$path'!\n");
}
} }
return 0; return 0;
...@@ -931,6 +963,10 @@ sub WhackDir($$) ...@@ -931,6 +963,10 @@ sub WhackDir($$)
my ($fs,$dir) = @_; my ($fs,$dir) = @_;
my $zfsfs = ""; my $zfsfs = "";
if (ClearNoDelete("$fs/$dir")) {
fatal("Could not clear no delete on '$fs/$dir'!\n");
}
if ($WITHZFS) { if ($WITHZFS) {
my $path = "${ZFS_ROOT}${fs}/$dir"; my $path = "${ZFS_ROOT}${fs}/$dir";
$zfsfs = $path $zfsfs = $path
...@@ -1098,3 +1134,30 @@ sub fatal($) { ...@@ -1098,3 +1134,30 @@ sub fatal($) {
print STDERR "$msg\n"; print STDERR "$msg\n";
exit(-1); exit(-1);
} }
#
# Use chflags on certain directories to prevent users from deleting things.
# Just a bandaid on the real problem.
#
sub SetNoDelete($)
{
my ($filename) = @_;
return 0
if (!$USEFLAGS);
system("$CHFLAGS sunlink $filename");
return ($? ? -1 : 0);
}
sub ClearNoDelete($)
{
my ($filename) = @_;
return 0
if (!$USEFLAGS);
# Do a recursive change here since we tend to do deletions on the
# top level directories.
system("$CHFLAGS -R nosunlink $filename");
return ($? ? -1 : 0);
}
...@@ -64,7 +64,7 @@ SBIN_STUFF = resetvlans console_setup.proxy sched_reload named_setup \ ...@@ -64,7 +64,7 @@ SBIN_STUFF = resetvlans console_setup.proxy sched_reload named_setup \
newnode_reboot savelogs.proxy eventsys.proxy \ newnode_reboot savelogs.proxy eventsys.proxy \
elabinelab panic node_attributes \ elabinelab panic node_attributes \
nfstrace plabinelab smbpasswd_setup smbpasswd_setup.proxy \ nfstrace plabinelab smbpasswd_setup smbpasswd_setup.proxy \
rmproj pool_daemon \ rmproj pool_daemon mkprojdirs \
checknodes_daemon snmpit.proxyv3 image_setup tcpp \ checknodes_daemon snmpit.proxyv3 image_setup tcpp \
arplockdown bscontrol reportboot reportboot_daemon \ arplockdown bscontrol reportboot reportboot_daemon \
nfsmfs_setup nfsmfs_setup.proxy manage_expsettings nfsmfs_setup nfsmfs_setup.proxy manage_expsettings
...@@ -130,7 +130,7 @@ SETUID_SBIN_SCRIPTS = mkproj rmgroup mkgroup frisbeehelper \ ...@@ -130,7 +130,7 @@ SETUID_SBIN_SCRIPTS = mkproj rmgroup mkgroup frisbeehelper \
rmuser idleswap named_setup exports_setup \ rmuser idleswap named_setup exports_setup \
sfskey_update setgroups newnode_reboot vnode_setup \ sfskey_update setgroups newnode_reboot vnode_setup \
elabinelab nfstrace rmproj arplockdown \ elabinelab nfstrace rmproj arplockdown \
bscontrol nfsmfs_setup bscontrol nfsmfs_setup mkprojdirs
SETUID_LIBX_SCRIPTS = console_setup spewrpmtar_verify SETUID_LIBX_SCRIPTS = console_setup spewrpmtar_verify
SETUID_SUEXEC_SCRIPTS= spewlogfile SETUID_SUEXEC_SCRIPTS= spewlogfile
......
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file 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 Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
use English;
use Getopt::Std;
use strict;
#
# Create (or recreate after dopey users remove things) the project
# directory hierarchy.
#
# This script always does the right thing, no need to run as admin.
#
sub usage()
{
print STDERR "Usage: mkprojdirs [-a] <pid> [<gid>]\n";
print STDERR " mkprojdirs [-a] -A\n";
print STDERR "Options:\n";
print STDERR " -a - Create (re-create) all subgroups for pid(s)\n";
print STDERR " -A - Create (re-create) all projects\n";
exit(-1);
}
my $optlist = "aA";
my $allpids = 0;
my $allgroups = 0;
my @projects = ();
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $CONTROL = "@USERNODE@";
my $SSH = "$TB/bin/sshtb";
my $ACCOUNTPROXY= "$TB/sbin/accountsetup";
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use emdb;
use libtestbed;
use Project;
use Group;
# Defined in libtestbed.
my $PROJROOT = PROJROOT();
my $GRPROOT = GROUPROOT();
# Protos
sub fatal($);
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
die("*** $0:\n".
" Must be setuid! Maybe its a development version?\n");
}
#
# This script is setuid, so please do not run it as root. Hard to track
# what has happened.
#
if ($UID == 0) {
die("*** $0:\n".
" Please do not run this as root! Its already setuid!\n");
}
#
# Check args.
#
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"a"})) {
$allgroups = 1;
}
if (defined($options{"A"})) {
$allpids = 1;
}
if ($allpids) {
my $query_result =
DBQueryFatal("select pid_idx from projects ".
"where approved=1");
if (! $query_result->numrows) {
fatal("No approved projects for -A option");
}
while (my ($pid_idx) = $query_result->fetchrow_array()) {
my $project = Project->Lookup($pid_idx);
if (!defined($project)) {
print STDERR "No database object for $pid_idx, skipping.\n";
next;
}
push(@projects, $project);
}
}
else {
usage()
if (@ARGV < 1);
my $project = Project->Lookup($ARGV[0]);
if (!defined($project)) {
fatal("No such project!")
}
@projects = ($project);
}
# Perl/ssh Sillyness.
$UID = $EUID;
foreach my $project (@projects) {
my $pid = $project->pid();
my @groups = ();
if ($allgroups) {
if ($project->GroupList(\@groups)) {
fatal("Could not get group list for project");
}
}
elsif (@ARGV > 1) {
my $group = $project->LookupGroup($ARGV[1]);
if (!defined($group)) {
fatal("Could not lookup group for project!")
}
push(@groups, $group);
}
else {
my $group = $project->GetProjectGroup();
if (!defined($group)) {
fatal("Could not lookup default group for project!")
}
push(@groups, $group);
}
if ($allpids) {
print "$pid\n";
}
#
# Run command on control node for each group.
#
foreach my $group (@groups) {
my $gid = $group->gid();
my $unix_gid = $group->unix_gid();
my $unix_name = $group->unix_name();
if ($allpids) {
print " $gid\n";
}
# Need proj/group leader for ownership.
my $leader = $group->GetLeader();
if (!defined($leader)) {
fatal("Could not determine group leader for $group");
}
my $unix_uid = $leader->unix_uid();
my $cmdstr;
if ($pid eq $gid) {
$cmdstr = "addproject $pid $unix_name $unix_gid $unix_uid";
}
else {
$cmdstr = "addgroup $gid $unix_name $unix_gid $unix_uid $pid";
}
if (system("$SSH -host $CONTROL $ACCOUNTPROXY $cmdstr")) {
fatal("Failed on $CONTROL: '$ACCOUNTPROXY $cmdstr'");
}
}
}
exit(0);
sub fatal($)
{
my ($msg) = @_;
die("*** $0:\n".
" $msg\n");
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment