the-bastion/bin/helper/osh-groupDelete
Stéphane Lesimple 3925e67d43 feat: add groupDestroy command for owners
This command deletes a group, as `groupDelete` does, but works
for owners so that they can delete their own group.
`groupDelete` remains as a restricted command, able to delete any group.

Closes #40.
2021-06-02 15:32:40 +02:00

206 lines
6.7 KiB
Perl
Executable file

#! /usr/bin/perl -T
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
# NEEDGROUP osh-groupDelete
# SUDOERS %osh-groupDelete ALL=(root) NOPASSWD:/usr/bin/env perl -T /opt/bastion/bin/helper/osh-groupDelete *
# KEYSUDOERS # as an owner, we can delete our own group
# KEYSUDOERS SUPEROWNERS, %%GROUP%-owner ALL=(root) NOPASSWD: /usr/bin/env perl -T %BASEPATH%/bin/helper/osh-groupDelete --group %GROUP%
# FILEMODE 0700
# FILEOWN 0 0
#>HEADER
use common::sense;
use Getopt::Long;
use File::Copy qw(move);
use File::Basename;
use lib dirname(__FILE__) . '/../../lib/perl';
use OVH::Bastion;
local $| = 1;
#
# Globals
#
$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/pkg/bin';
my ($self) = $ENV{'SUDO_USER'} =~ m{^([a-zA-Z0-9._-]+)$};
if (not defined $self) {
if ($< == 0) {
$self = 'root';
}
else {
HEXIT('ERR_SUDO_NEEDED', msg => 'This command must be run under sudo');
}
}
# Fetch command options
my $fnret;
my ($result, @optwarns);
my ($group);
eval {
local $SIG{__WARN__} = sub { push @optwarns, shift };
$result = GetOptions("group=s" => sub { $group //= $_[1] },);
};
if ($@) { die $@ }
if (!$result) {
local $" = ", ";
HEXIT('ERR_BAD_OPTIONS', msg => "Error parsing options: @optwarns");
}
if (!$group) {
HEXIT('ERR_MISSING_PARAMETER', msg => "Missing argument 'group'");
}
#<HEADER
#>PARAMS:GROUP
# test if start by key, append if necessary
osh_debug("Checking group");
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key");
$fnret or HEXIT($fnret);
# get returned untainted value
$group = $fnret->value->{'group'};
my $shortGroup = $fnret->value->{'shortGroup'};
#<PARAMS:GROUP
#>RIGHTSCHECK
if ($self eq 'root') {
osh_debug "Real root, skipping checks of permissions";
}
else {
# either we can delete any group
$fnret = OVH::Bastion::is_user_in_group(user => $self, group => "osh-groupDelete");
if (!$fnret) {
# or we can delete our own group as the owner of said group
$fnret = OVH::Bastion::is_group_owner(account => $self, group => $shortGroup, sudo => 1, superowner => 1);
if (!$fnret) {
HEXIT('ERR_SECURITY_VIOLATION', msg => "You're not allowed to run this, dear $self");
}
}
}
#<RIGHTSCHECK
#>CODE
# last security check
if (not -e "/home/$group/allowed.ip" or not -e "/home/keykeeper/$group") {
HEXIT('ERR_INVALID_GROUP', msg => "Sorry, but $shortGroup doesn't seem to be a legit bastion group");
}
if (not -d "/home/oldkeeper") {
mkdir "/home/oldkeeper";
}
chown 0, 0, "/home/oldkeeper";
chmod 0700, "/home/oldkeeper";
if (!-d "/home/oldkeeper/groups") {
mkdir "/home/oldkeeper/groups";
}
chown 0, 0, "/home/oldkeeper/groups";
chmod 0700, "/home/oldkeeper/groups";
my $suffix = 'at-' . time() . '.by-' . $self;
my $fulldir = "/home/oldkeeper/groups/$group.$suffix";
if (-e $fulldir) {
exitError("Errr... $fulldir exists?!");
}
mkdir $fulldir;
chown 0, 0, $fulldir;
chmod 0700, $fulldir;
move("/home/$group", "$fulldir/$group-home");
move("/home/keykeeper/$group", "$fulldir/$group-keykeeper");
# now tar.gz the directory, this is important because inside we'll keep the
# old GID of the group, and we don't want GID-orphaned files on our filesystem, it's
# not a problem to have those inside a tarfile however.
my @tarcmd = qw{ tar czf };
push @tarcmd, $fulldir . '.tar.gz';
push @tarcmd, '--acls' if OVH::Bastion::has_acls();
push @tarcmd, '--one-file-system', '-p', '--remove-files', $fulldir;
$fnret = OVH::Bastion::execute(cmd => \@tarcmd, must_succeed => 1);
if (!$fnret) {
osh_warn("Couldn't tar the backup homedir of this group (" . $fnret->msg . "), proceeding anyway.");
chmod 0000, $fulldir;
}
else {
chmod 0000, $fulldir . '.tar.gz';
unlink($fulldir);
}
# remove dead symlinks in users homes
my $dh;
if (opendir($dh, "/home/allowkeeper")) {
while (my $dir = readdir($dh)) {
$dir =~ /^\./ and next;
$dir !~ /^([a-zA-Z0-9._-]+)$/ and next;
$dir = "/home/allowkeeper/$1"; # and untaint
-d $dir or next;
foreach my $file ("$dir/allowed.ip.$shortGroup", "$dir/allowed.partial.$shortGroup") {
if (-e $file || -l $file) {
osh_info "Removing $file...";
unlink($file);
}
}
}
close($dh);
}
else {
osh_warn("Couldn't open /home/allowkeeper ?!");
}
# trying to remove main and gatekeeper and owner groups
foreach my $todelete ("$group-owner", "$group-aclkeeper", "$group-gatekeeper", $group) {
$fnret = OVH::Bastion::is_group_existing(group => $todelete);
if ($fnret) {
$todelete = $fnret->value->{'group'}; # untaint
my $members = $fnret->value->{'members'} || [];
if (@$members) {
osh_info "Found " . (scalar @$members) . " members, removing them from the group";
foreach my $member (@$members) {
osh_info "... removing $member from group $todelete";
$fnret = OVH::Bastion::sys_delmemberfromgroup(user => $member, group => $todelete, noisy_stderr => 1);
$fnret->err eq 'OK'
or HEXIT('ERR_DELUSER_FAILED', msg => "Error while attempting to remove member $member from group $todelete (" . $fnret->msg . ")");
}
}
if ($todelete eq $group) {
osh_info "Deleting main user of group $todelete...", $fnret = OVH::Bastion::sys_userdel(user => $todelete, noisy_stderr => 1);
$fnret->err eq 'OK'
or HEXIT('ERR_DELUSER_FAILED', msg => "Error while attempting to delete main user of group $todelete (" . $fnret->msg . ")");
}
# some OSes delete the main group of user if it has the same name
# and nobody else is a member of it, so check it still exists before
# trying to delete it
$fnret = OVH::Bastion::is_group_existing(group => $todelete);
if ($fnret) {
osh_info "Deleting group $todelete...";
$fnret = OVH::Bastion::sys_groupdel(group => $todelete, noisy_stderr => 1);
$fnret
or HEXIT('ERR_DELGROUP_FAILED', msg => "Error while attempting to delete group $todelete (" . $fnret->msg . ")");
}
}
else {
osh_info "Group $todelete not found, ignoring...";
}
}
# remove sudoers if it's there
$fnret = OVH::Bastion::execute(cmd => [$OVH::Bastion::BASEPATH . '/bin/sudogen/generate-sudoers.sh', 'delete', 'group', $group], must_succeed => 1, noisy_stdout => 1);
if (!$fnret) {
warn_syslog("Error during group deletion of '$group', couldn't delete sudoers file: " . $fnret->msg);
}
OVH::Bastion::syslogFormatted(
severity => 'info',
type => 'group',
fields => [['action', 'delete'], ['group', $shortGroup],]
);
HEXIT('OK');