mirror of
https://github.com/ovh/the-bastion.git
synced 2024-12-29 11:41:27 +08:00
82f2c96ea6
Signed-off-by: Antoine Leblanc <antoine.leblanc@ovhcloud.com>
222 lines
7.3 KiB
Perl
Executable file
222 lines
7.3 KiB
Perl
Executable file
#! /usr/bin/perl -T
|
|
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
|
# NEEDGROUP osh-accountDelete
|
|
# SUDOERS %osh-accountDelete ALL=(root) NOPASSWD:/usr/bin/env perl -T /opt/bastion/bin/helper/osh-accountDelete *
|
|
# 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 ($account, $type);
|
|
eval {
|
|
local $SIG{__WARN__} = sub { push @optwarns, shift };
|
|
$result = GetOptions(
|
|
"account=s" => sub { $account //= $_[1] },
|
|
"type=s" => sub { $type //= $_[1] },
|
|
);
|
|
};
|
|
if ($@) { die $@ }
|
|
|
|
if (!$result) {
|
|
local $" = ", ";
|
|
HEXIT('ERR_BAD_OPTIONS', msg => "Error parsing options: @optwarns");
|
|
}
|
|
|
|
if (!$account || !$type) {
|
|
HEXIT('ERR_MISSING_PARAMETER', msg => "Missing argument 'account' or 'type'");
|
|
}
|
|
|
|
#<HEADER
|
|
|
|
#>RIGHTSCHECK
|
|
if ($self eq 'root') {
|
|
osh_debug "Real root, skipping checks of permissions";
|
|
}
|
|
else {
|
|
# need to perform another security check
|
|
$fnret = OVH::Bastion::is_user_in_group(user => $self, group => "osh-accountDelete");
|
|
if (!$fnret) {
|
|
HEXIT('ERR_SECURITY_VIOLATION', msg => "You're not allowed to run this, dear $self");
|
|
}
|
|
}
|
|
|
|
#<RIGHTSCHECK
|
|
|
|
#>PARAMS:TYPE
|
|
osh_debug("Checking type");
|
|
if (not grep { $type eq $_ } qw{ normal realm }) {
|
|
HEXIT('ERR_INVALID_PARAMETER', "Expected type 'normal' or 'realm'");
|
|
}
|
|
|
|
#<PARAMS:TYPE
|
|
|
|
#>PARAMS:ACCOUNT
|
|
osh_debug("Checking account");
|
|
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $account, accountType => $type);
|
|
$fnret or HEXIT($fnret);
|
|
|
|
# get returned untainted value
|
|
$account = $fnret->value->{'account'};
|
|
|
|
#>CODE
|
|
# don't allow a non-admin deleting an admin
|
|
if (OVH::Bastion::is_admin(account => $account, sudo => 1) && !OVH::Bastion::is_admin(account => $self, sudo => 1)) {
|
|
HEXIT('ERR_SECURITY_VIOLATION', msg => "You can't delete an admin without being admin yourself");
|
|
}
|
|
|
|
# Try to find the user -tty group
|
|
my $ttygroup = "$account-tty";
|
|
if (!getgrnam($ttygroup)) {
|
|
$ttygroup = substr($account, 0, 5) . '-tty';
|
|
if (!getgrnam($ttygroup)) {
|
|
osh_warn("Couldn't find out which is the tty group of this account, will still delete it anyway");
|
|
warn_syslog("Couldn't find out the name of the tty group of account $account ($ttygroup doesn't exist), deleting anyway");
|
|
$ttygroup = undef;
|
|
}
|
|
}
|
|
|
|
# last security check
|
|
if ((getpwnam($account))[8] !~ m{/osh\.pl$}) {
|
|
HEXIT('ERR_INVALID_ACCOUNT', msg => "Account $account doesn't seem to be a legit bastion account");
|
|
}
|
|
|
|
# kill all user processes, if any
|
|
# GNU and BSD compliant
|
|
$fnret = OVH::Bastion::execute(cmd => ['ps', '-U', $account, '-o', 'pid'], noisy_stderr => 1);
|
|
if ($fnret->err ne 'OK' || ref $fnret->value->{'stdout'} ne 'ARRAY') {
|
|
; # don't warn, we can get an return code of 1 just because there are no processes matching
|
|
}
|
|
else {
|
|
# don't check kill return because it may fail if the process died since,
|
|
# and it's not a big issue, we'll still delete the account
|
|
# we have to untaint what `ps` gave us however
|
|
my @pids;
|
|
foreach my $pid (@{$fnret->value->{'stdout'}}) {
|
|
push @pids, $1 if ($pid =~ m{(\d+)});
|
|
}
|
|
kill 'KILL', @pids if @pids;
|
|
}
|
|
|
|
# do the stuff
|
|
if (!-d "/home/oldkeeper") {
|
|
mkdir "/home/oldkeeper";
|
|
}
|
|
chown 0, 0, "/home/oldkeeper";
|
|
chmod 0700, "/home/oldkeeper";
|
|
|
|
if (!-d "/home/oldkeeper/accounts") {
|
|
mkdir "/home/oldkeeper/accounts";
|
|
}
|
|
chown 0, 0, "/home/oldkeeper/accounts";
|
|
chmod 0700, "/home/oldkeeper/accounts";
|
|
|
|
my $suffix = 'at-' . time() . '.by-' . $self;
|
|
|
|
my $fulldir = "/home/oldkeeper/accounts/$account.$suffix";
|
|
if (-e $fulldir) {
|
|
HEXIT('ERR_BACKUP_DIR_COLLISION', msg => "This shouldn't happen, $fulldir already exists!");
|
|
}
|
|
|
|
mkdir $fulldir;
|
|
chown 0, 0, $fulldir;
|
|
chmod 0700, $fulldir;
|
|
|
|
move("/home/$account", "$fulldir/$account-home");
|
|
move("/home/allowkeeper/$account", "$fulldir/allowkeeper");
|
|
|
|
# remove +a or tar won't be able to rm files, don't check if it succeeded if we're on a system without chattr
|
|
$fnret = OVH::Bastion::execute(cmd => ['find', "$fulldir/$account-home", '-maxdepth', '1', '-name', "*.log", '-exec', 'chattr', '-a', '{}', ';']);
|
|
|
|
# remove sudoers if it's there
|
|
$fnret = OVH::Bastion::execute(cmd => [$OVH::Bastion::BASEPATH . '/bin/sudogen/generate-sudoers.sh', 'delete', 'account', $account], must_succeed => 1, noisy_stdout => 1);
|
|
if (!$fnret) {
|
|
warn_syslog("Error during account deletion of '$account', couldn't delete sudoers file: " . $fnret->msg);
|
|
}
|
|
|
|
# add a text file with all the groups the user was a member of
|
|
$fnret = OVH::Bastion::get_user_groups(account => $account, extra => 1);
|
|
if ($fnret) {
|
|
if (open(my $txtfile, '>', "$fulldir/groups.txt")) {
|
|
print $txtfile join("\n", @{$fnret->value});
|
|
close($txtfile);
|
|
}
|
|
else {
|
|
osh_warn("Couldn't open the groups.txt file to save the group list of this account ($!)");
|
|
}
|
|
}
|
|
|
|
# now tar.gz the directory, this is important because inside we'll keep the
|
|
# old UID of the user, and we don't want UID-orphaned 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;
|
|
|
|
osh_info("Backing up home directory...");
|
|
$fnret = OVH::Bastion::execute(cmd => \@tarcmd, must_succeed => 1);
|
|
if (!$fnret) {
|
|
osh_warn("Couldn't tar the backup homedir of this account (" . $fnret->msg . "), proceeding anyway.");
|
|
chmod 0000, $fulldir;
|
|
}
|
|
else {
|
|
chmod 0000, $fulldir . '.tar.gz';
|
|
unlink($fulldir);
|
|
}
|
|
osh_info("Backup done");
|
|
|
|
osh_info "Removing '$account' group membership from 'keyreader' user";
|
|
$fnret = OVH::Bastion::sys_delmemberfromgroup(user => "keyreader", group => $account);
|
|
$fnret or HEXIT($fnret);
|
|
osh_info "Deleting system user '$account'...";
|
|
$fnret = OVH::Bastion::sys_userdel(user => $account);
|
|
$fnret or HEXIT($fnret);
|
|
|
|
# some systems don't delete the primary group with userdel (suse at least)
|
|
$fnret = OVH::Bastion::execute(cmd => ['getent', 'group', $account]);
|
|
if ($fnret && $fnret->value->{'sysret'} == 0) {
|
|
osh_info "Deleting account main group '$account'...";
|
|
$fnret = OVH::Bastion::sys_groupdel(group => $account);
|
|
$fnret or HEXIT($fnret);
|
|
}
|
|
|
|
if (defined $ttygroup) {
|
|
osh_info "Deleting group $ttygroup...";
|
|
$fnret = OVH::Bastion::sys_groupdel(group => $ttygroup);
|
|
$fnret or HEXIT($fnret);
|
|
}
|
|
|
|
OVH::Bastion::syslogFormatted(
|
|
severity => 'info',
|
|
type => 'account',
|
|
fields => [['action', 'delete'], ['account', $account], ['tty_group', $ttygroup],]
|
|
);
|
|
|
|
HEXIT('OK', value => {account => $account, ttygroup => $ttygroup, operation => 'deleted'});
|