mirror of
https://github.com/ovh/the-bastion.git
synced 2025-02-27 00:54:26 +08:00
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
114 lines
3.4 KiB
Perl
Executable file
114 lines
3.4 KiB
Perl
Executable file
#! /usr/bin/perl -T
|
|
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
|
# FILEMODE 0700
|
|
# FILEOWN 0 0
|
|
|
|
#>HEADER
|
|
use common::sense;
|
|
use Getopt::Long;
|
|
|
|
use File::Basename;
|
|
use lib dirname(__FILE__) . '/../../lib/perl';
|
|
use OVH::Result;
|
|
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, $step);
|
|
eval {
|
|
local $SIG{__WARN__} = sub { push @optwarns, shift };
|
|
$result = GetOptions(
|
|
"account=s" => sub { $account //= $_[1] },
|
|
"step=i" => sub { $step //= $_[1] },
|
|
);
|
|
};
|
|
if ($@) { die $@ }
|
|
|
|
if (!$result) {
|
|
local $" = ", ";
|
|
HEXIT('ERR_BAD_OPTIONS', msg => "Error parsing options: @optwarns");
|
|
}
|
|
|
|
if (!$account || !defined $step) {
|
|
HEXIT('ERR_MISSING_PARAMETER', msg => "Missing argument 'account' or 'step'");
|
|
}
|
|
|
|
#>RIGHTSCHECK
|
|
if ($self eq 'root') {
|
|
osh_debug "Real root, skipping checks of permissions";
|
|
}
|
|
elsif ($self ne $account) {
|
|
HEXIT('ERR_SECURITY_VIOLATION', msg => "You're not allowed to run this, dear $self");
|
|
}
|
|
|
|
#<RIGHTSCHECK
|
|
|
|
#>PARAMS:ACCOUNT
|
|
osh_debug("Checking account");
|
|
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $account);
|
|
$fnret or HEXIT($fnret);
|
|
$account = $fnret->value->{'account'}; # untainted
|
|
|
|
#<PARAMS:ACCOUNT
|
|
|
|
if ($step == 0) {
|
|
|
|
# get password status
|
|
HEXIT(OVH::Bastion::sys_getpasswordinfo(user => $account));
|
|
}
|
|
elsif ($step == 1) {
|
|
|
|
# set a temporary password
|
|
my $password = sprintf("%04d-%04d-%04d-%04d", rand(10000), rand(10000), rand(10000), rand(10000));
|
|
$fnret = OVH::Bastion::sys_changepassword(user => $account, password => $password);
|
|
$fnret or HEXIT($fnret);
|
|
|
|
# force password change in 1 day max (it should be done several seconds after anyway)
|
|
$fnret = OVH::Bastion::sys_setpasswordpolicy(
|
|
user => $account,
|
|
inactiveDays => OVH::Bastion::config('MFAPasswordInactiveDays')->value,
|
|
minDays => 0,
|
|
maxDays => 1,
|
|
warnDays => 1
|
|
);
|
|
$fnret or HEXIT($fnret);
|
|
|
|
HEXIT('OK', value => {password => $password});
|
|
}
|
|
|
|
elsif ($step == 2) {
|
|
$fnret = OVH::Bastion::sys_addmembertogroup(user => $account, group => OVH::Bastion::MFA_PASSWORD_CONFIGURED_GROUP);
|
|
$fnret or HEXIT($fnret);
|
|
|
|
# now that we have the final password set, apply the bastionwide password policy
|
|
$fnret = OVH::Bastion::sys_setpasswordpolicy(
|
|
user => $account,
|
|
inactiveDays => OVH::Bastion::config('MFAPasswordInactiveDays')->value,
|
|
minDays => OVH::Bastion::config('MFAPasswordMinDays')->value,
|
|
maxDays => OVH::Bastion::config('MFAPasswordMaxDays')->value,
|
|
warnDays => OVH::Bastion::config('MFAPasswordWarnDays')->value
|
|
);
|
|
$fnret or HEXIT($fnret);
|
|
|
|
HEXIT('OK');
|
|
}
|
|
|
|
HEXIT('ERR_INVALID_PARAMETER', msg => "Parameter --step expects 0, 1 or 2");
|