2020-10-16 00:32:37 +08:00
|
|
|
package OVH::Bastion::Plugin::generatePassword;
|
|
|
|
|
|
|
|
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
|
|
|
|
use common::sense;
|
|
|
|
|
|
|
|
use File::Basename;
|
|
|
|
use lib dirname(__FILE__) . '/../../../../../lib/perl';
|
|
|
|
use OVH::Result;
|
|
|
|
use OVH::Bastion;
|
|
|
|
|
|
|
|
sub preconditions {
|
|
|
|
my %params = @_;
|
|
|
|
my $self = $params{'self'};
|
|
|
|
my $sudo = $params{'sudo'};
|
|
|
|
my $group = $params{'group'};
|
|
|
|
my $account = $params{'account'};
|
|
|
|
my $size = $params{'size'};
|
|
|
|
my $context = $params{'context'};
|
|
|
|
|
|
|
|
my $fnret;
|
|
|
|
my ($shortGroup, $passhome, $base);
|
|
|
|
|
|
|
|
if (!$size || !$context) {
|
|
|
|
return R('ERR_MISSING_PARAMETER', msg => "Missing argument 'size' or 'context'");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($context eq 'group') {
|
|
|
|
if (not $group) {
|
|
|
|
return R('ERR_MISSING_PARAMETER', msg => "Missing argument 'group'");
|
|
|
|
}
|
|
|
|
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => 'key');
|
|
|
|
$fnret or return $fnret;
|
|
|
|
$group = $fnret->value->{'group'};
|
|
|
|
$shortGroup = $fnret->value->{'shortGroup'};
|
|
|
|
$passhome = "/home/$group/pass";
|
|
|
|
$base = "$passhome/$shortGroup";
|
|
|
|
}
|
|
|
|
elsif ($context eq 'account') {
|
|
|
|
if (not $account) {
|
|
|
|
return R('ERR_MISSING_PARAMETER', msg => "Missing argument 'account'");
|
|
|
|
}
|
|
|
|
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $account);
|
|
|
|
$fnret or return $fnret;
|
|
|
|
$account = $fnret->value->{'account'};
|
|
|
|
$passhome = "/home/$account/pass";
|
|
|
|
$base = "$passhome/$account";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return R('ERR_INVALID_PARAMETER', msg => "Expected a context 'group' or 'account'");
|
|
|
|
}
|
|
|
|
|
|
|
|
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $self);
|
|
|
|
$fnret or return $fnret;
|
|
|
|
$self = $fnret->value->{'account'};
|
|
|
|
|
|
|
|
return R('ERR_INVALID_PARAMETER', msg => "The argument 'size' must be an integer") if $size !~ /^\d+$/;
|
|
|
|
return R('ERR_INVALID_PARAMETER', msg => "Specified size must be >= 8") if $size < 8;
|
|
|
|
return R('ERR_INVALID_PARAMETER', msg => "Specified size must be <= 127") if $size > 128;
|
|
|
|
|
|
|
|
if ($context eq 'account' && $self ne $account) {
|
|
|
|
$fnret = OVH::Bastion::is_user_in_group(user => $self, group => "osh-accountGeneratePassword");
|
|
|
|
$fnret or HEXIT('ERR_SECURITY_VIOLATION', msg => "You're not allowed to run this, dear $self");
|
|
|
|
}
|
|
|
|
elsif ($context eq 'group') {
|
|
|
|
$fnret = OVH::Bastion::is_group_owner(account => $self, group => $shortGroup, superowner => 1, sudo => $sudo);
|
|
|
|
$fnret or HEXIT('ERR_NOT_ALLOWED', msg => "You're not a group owner of $shortGroup, dear $self");
|
|
|
|
}
|
|
|
|
|
|
|
|
# return untainted values
|
|
|
|
return R(
|
|
|
|
'OK',
|
|
|
|
value => {
|
|
|
|
self => $self,
|
|
|
|
account => $account,
|
|
|
|
shortGroup => $shortGroup,
|
|
|
|
group => $group,
|
|
|
|
size => $size,
|
|
|
|
context => $context,
|
|
|
|
passhome => $passhome,
|
|
|
|
base => $base
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub act {
|
|
|
|
my %params = @_;
|
|
|
|
my $fnret = preconditions(%params);
|
|
|
|
$fnret or return $fnret;
|
|
|
|
|
|
|
|
my %values = %{$fnret->value()};
|
|
|
|
my ($self, $account, $shortGroup, $group, $size, $passhome, $base, $context, $passhome, $base) =
|
|
|
|
@values{qw{ self account shortGroup group size passhome base context passhome base }};
|
|
|
|
|
|
|
|
my $pass;
|
|
|
|
my $antiloop = 1000;
|
|
|
|
|
|
|
|
my $hashes;
|
|
|
|
RETRY: while ($antiloop-- > 0) {
|
|
|
|
|
|
|
|
# generate a password
|
|
|
|
$pass = '';
|
|
|
|
foreach (1 .. $size) {
|
|
|
|
$pass .= chr(int(rand(ord('~') - ord('!')) + ord('!')));
|
|
|
|
}
|
|
|
|
|
|
|
|
# get the corresponding hashes
|
|
|
|
$fnret = OVH::Bastion::get_hashes_from_password(password => $pass);
|
|
|
|
$fnret or HEXIT($fnret);
|
|
|
|
|
|
|
|
# verify that the hashes match this regex (some constructors need it)
|
|
|
|
my $check_re = qr'^\$\d\$[a-zA-Z0-9]+\$[a-zA-Z0-9.\/]+$';
|
|
|
|
foreach my $hash (keys %{$fnret->value}) {
|
|
|
|
next RETRY if ($fnret->value->{$hash} && $fnret->value->{$hash} !~ $check_re);
|
|
|
|
}
|
|
|
|
|
|
|
|
$hashes = $fnret->value;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ref $hashes ne 'HASH') {
|
|
|
|
return R('ERR_INTERNAL', msg => "Couldn't generate a valid password");
|
|
|
|
}
|
|
|
|
|
|
|
|
# push password in a file
|
|
|
|
if (!-d $passhome) {
|
|
|
|
if (!mkdir $passhome) {
|
|
|
|
HEXIT('ERR_INTERNAL', msg => "Couldn't create passwords directory in group home '$passhome' ($!)");
|
|
|
|
}
|
|
|
|
if ($context eq 'account') {
|
|
|
|
if (my (undef, undef, $uid, $gid) = getpwnam($account)) {
|
|
|
|
chown $uid, $gid, $passhome;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!-d $passhome) {
|
|
|
|
HEXIT('ERR_INTERNAL', msg => "Couldn't create passwords directory in group home");
|
|
|
|
}
|
|
|
|
chmod 0750, $passhome;
|
|
|
|
if (-e $base) {
|
|
|
|
|
|
|
|
# rotate old passwords
|
|
|
|
unlink "$base.99";
|
|
|
|
foreach my $i (1 .. 98) {
|
|
|
|
my $n = 99 - $i;
|
|
|
|
my $next = $n + 1;
|
|
|
|
if (-e "$base.$n") {
|
|
|
|
osh_debug "renaming $base.$n to $base.$next";
|
|
|
|
if (!rename "$base.$n", "$base.$next") {
|
2020-11-06 01:36:17 +08:00
|
|
|
HEXIT('ERR_INTERNAL', msg => "Couldn't rename '$base.$n' to '$base.$next' ($!)");
|
2020-10-16 00:32:37 +08:00
|
|
|
}
|
|
|
|
if (-e "$base.$n.metadata" && !rename "$base.$n.metadata", "$base.$next.metadata") {
|
2020-11-06 01:36:17 +08:00
|
|
|
HEXIT('ERR_INTERNAL', msg => "Couldn't rename '$base.$n.metadata' to '$base.$next.metadata' ($!)");
|
2020-10-16 00:32:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
osh_debug "renaming $base to $base.1";
|
|
|
|
if (!rename "$base", "$base.1") {
|
2020-11-06 01:36:17 +08:00
|
|
|
HEXIT('ERR_INTERNAL', msg => "Couldn't rename '$base' to '$base.1' ($!)");
|
2020-10-16 00:32:37 +08:00
|
|
|
}
|
|
|
|
if (-e "$base.metadata" && !rename "$base.metadata", "$base.1.metadata") {
|
2020-11-06 01:36:17 +08:00
|
|
|
HEXIT('ERR_INTERNAL', msg => "Couldn't rename '$base.metadata' to '$base.1.metadata' ($!)");
|
2020-10-16 00:32:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (open(my $fdout, '>', $base)) {
|
|
|
|
print $fdout "$pass\n";
|
|
|
|
close($fdout);
|
|
|
|
if ($context eq 'account') {
|
|
|
|
if (my (undef, undef, $uid, $gid) = getpwnam($account)) {
|
|
|
|
chown $uid, $gid, $base;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
chmod 0440, $base;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
HEXIT('ERR_INTERNAL', msg => "Couldn't create password file in $base ($!)");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (open(my $fdout, '>', "$base.metadata")) {
|
|
|
|
print $fdout "CREATED_BY=$self\nBASTION_VERSION=" . $OVH::Bastion::VERSION . "\nCREATION_TIME=" . localtime() . "\nCREATION_TIMESTAMP=" . time() . "\n";
|
|
|
|
close($fdout);
|
|
|
|
if ($context eq 'account') {
|
|
|
|
if (my (undef, undef, $uid, $gid) = getpwnam($account)) {
|
|
|
|
chown $uid, $gid, "$base.metadata";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
chmod 0440, "$base.metadata";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
osh_warn "Couldn't create metadata file, proceeding anyway";
|
|
|
|
}
|
|
|
|
|
|
|
|
return R('OK', value => {context => $context, group => $shortGroup, account => $account, hashes => $hashes});
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|