mirror of
https://github.com/ovh/the-bastion.git
synced 2025-01-04 06:27:11 +08:00
2c2f723bbb
To avoid having e.g. a group creation interrupted in the middle just because the caller killed their ssh connection while we're still working
208 lines
6.9 KiB
Perl
Executable file
208 lines
6.9 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
|
|
#
|
|
$SIG{'HUP'} = 'IGNORE'; # continue even when attached terminal is closed (we're called with setsid on supported systems anyway)
|
|
$SIG{'PIPE'} = 'IGNORE'; # continue even if osh_info gets a SIGPIPE because there's no longer a terminal
|
|
$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');
|