feat: add groupGenerateEgressKey and groupDelEgressKey

pull/143/head
Stéphane Lesimple 5 years ago committed by Stéphane Lesimple
parent fe58cf1d14
commit e760cf6142

@ -0,0 +1,133 @@
#! /usr/bin/perl -T
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
# KEYSUDOERS # as an owner, we can delete an egress key of the group
# KEYSUDOERS SUPEROWNERS, %%GROUP%-owner ALL=(keykeeper) NOPASSWD: /usr/bin/env perl -T %BASEPATH%/bin/helper/osh-groupDelEgressKey --group %GROUP% *
# FILEMODE 0750
# FILEOWN 0 keykeeper
#>HEADER
use common::sense;
use Getopt::Long;
use File::Basename;
use lib dirname(__FILE__) . '/../../lib/perl';
use OVH::Bastion;
use OVH::Result;
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
Getopt::Long::Configure("no_auto_abbrev");
my $fnret;
my ($result, @optwarns);
my ($group, $id);
eval {
local $SIG{__WARN__} = sub { push @optwarns, shift };
$result = GetOptions(
"group=s" => sub { $group //= $_[1] }, # ignore subsequent --group on cmdline (anti-sudoers-override)
"id=s" => sub { $id //= $_[1] },
);
};
if ($@) { die $@ }
if (!$result) {
local $" = ", ";
HEXIT('ERR_BAD_OPTIONS', msg => "Error parsing options: @optwarns");
}
if (!$group || !$id) {
HEXIT('ERR_MISSING_PARAMETER', msg => "Missing argument 'group' or 'id'");
}
#<HEADER
#>PARAMS: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";
}
$fnret = OVH::Bastion::is_group_owner(account => $self, group => $shortGroup, superowner => 1, sudo => 1);
if (!$fnret) {
HEXIT('ERR_SECURITY_VIOLATION', msg => "You're not allowed to run this, dear $self");
}
#<RIGHTSCHECK
#>CODE
$fnret = OVH::Bastion::get_group_keys(group => $group);
$fnret or HEXIT($fnret);
my @matchingKeys = grep { $fnret->value->{'keys'}{$_}{'id'} eq $id } @{$fnret->value->{'sortedKeys'} || []};
if (!@matchingKeys) {
HEXIT('ERR_INVALID_PARAMETER', msg => "Couldn't find any key with the ID you specified ($id) in group $shortGroup");
}
my $keyToDelete = $matchingKeys[0];
my $key = $fnret->value->{'keys'}{$keyToDelete};
osh_info("We're about to delete the following key:\n");
OVH::Bastion::print_public_key(key => $key);
# get the path to the privkey
my $fileToDelete = $fnret->value->{'keys'}{$keyToDelete}{'fullpath'};
if (!-f $fileToDelete) {
warn_syslog("The file '$fileToDelete' doesn't exist while trying to delete this egress key from group $shortGroup");
HEXIT('ERR_INVALID_PARAMETER', msg => "Couldn't find the key file");
}
my @errors;
foreach my $file ($fileToDelete, "$fileToDelete.pub") {
push @errors, "Couldn't delete '$file' in groupDelEgressKey by $self ($!)" if !unlink($file);
}
if (@errors) {
warn_syslog($_) for @errors;
if (@errors == 2) {
HEXIT('ERR_INTERNAL', msg => "Couldn't delete the requested key, more information available in the system log");
}
HEXIT('ERR_INTERNAL', msg => "Couldn't delete one of the files constituting the key, more information available in the system log");
}
OVH::Bastion::syslogFormatted(
severity => 'info',
type => 'group',
fields => [
[action => 'delete_egress_key'],
[group => $shortGroup],
[self => $self],
[key_id => $key->{'id'}],
[key_algo => $key->{'typecode'}],
[key_algo_family => $key->{'family'}],
[key_size => $key->{'size'}],
[key_fingerprint => $key->{'fingerprint'}],
[key_comment => $key->{'comment'}],
[key_mtime => $key->{'mtime'}],
[key_path => $key->{'fullpath'}],
[key_base64 => $key->{'base64'}],
]
);
HEXIT('OK', value => $key, msg => "Key $id has successfully been deleted from the $shortGroup group");

@ -0,0 +1,138 @@
#! /usr/bin/perl -T
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
# KEYSUDOERS # as an owner, we can generate an egress key for the group
# KEYSUDOERS SUPEROWNERS, %%GROUP%-owner ALL=(root) NOPASSWD: /usr/bin/env perl -T %BASEPATH%/bin/helper/osh-groupGenerateEgressKey --group %GROUP% *
# FILEMODE 0755
# 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;
use OVH::Bastion::Plugin::generateEgressKey;
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 ($result, @optwarns);
my ($group, $algo, $size, $encrypted);
eval {
local $SIG{__WARN__} = sub { push @optwarns, shift };
$result = GetOptions(
"group=s" => sub { $group //= $_[1] }, # ignore subsequent --group on cmdline (anti-sudoers-override)
"algo=s" => sub { $algo //= $_[1] },
"size=i" => sub { $size //= $_[1] },
"encrypted" => sub { $encrypted //= $_[1] },
);
};
if ($@) { die $@ }
if (!$result) {
local $" = ", ";
HEXIT('ERR_BAD_OPTIONS', msg => "Error parsing options: @optwarns");
}
if (!$size || !$algo || !$group) {
HEXIT('ERR_MISSING_PARAMETER', msg => "Missing argument 'size', 'algo' or 'group'");
}
#<HEADER
my $fnret;
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key");
$fnret or HEXIT($fnret);
$fnret = OVH::Bastion::Plugin::generateEgressKey::preconditions(
context => 'group',
self => $self,
group => $group,
algo => $algo,
size => $size,
sudo => 1,
);
$fnret or HEXIT($fnret);
# get returned untainted values
my ($shortGroup, $keyhome);
($group, $algo, $size, $shortGroup, $keyhome) = @{$fnret->value}{qw{ group algo size shortGroup keyhome}};
my $passphrase = '';
if ($encrypted) {
# read the passphrase from stdin
$passphrase = <STDIN>;
# we need to untaint it, as it's going to be passed as an arg to the array version of system(),
# it can contain anything, really, there is no shell escape possible (see generate_ssh_key in ssh.inc)
($passphrase) = $passphrase =~ /^(.+)$/;
}
my $keykeeper_uid = (getpwnam('keykeeper'))[2];
my $group_gid = (getgrnam($group))[2];
if (!$keykeeper_uid || !$group_gid) {
warn_syslog("Couldn't get the uid of keykeeper ($keykeeper_uid) or gid of $group ($group_gid) while $self is attempting to generate a new key with algo $algo and size $size");
HEXIT('ERR_INTERNAL', msg => "Couldn't fetch the required account or group IDs");
}
osh_info "Generating a new key pair, this might take a while...";
$fnret = OVH::Bastion::generate_ssh_key(
folder => $keyhome,
prefix => $shortGroup,
algo => $algo,
size => $size,
passphrase => $passphrase,
uid => $keykeeper_uid,
gid => $group_gid,
group_readable => 1,
);
$fnret or HEXIT($fnret);
my $filepath = $fnret->value->{'file'};
my $mtime = (stat($filepath))[9];
osh_info "The new key pair has been generated:\n";
$fnret = OVH::Bastion::get_ssh_pub_key_info(file => $filepath . ".pub", way => "egress");
$fnret or HEXIT($fnret);
my $key = $fnret->value;
OVH::Bastion::syslogFormatted(
severity => 'info',
type => 'group',
fields => [
[action => 'generate_egress_key'],
[group => $shortGroup],
[self => $self],
[key_id => $key->{'id'}],
[key_algo => $key->{'typecode'}],
[key_algo_family => $key->{'family'}],
[key_size => $key->{'size'}],
[key_fingerprint => $key->{'fingerprint'}],
[key_comment => $key->{'comment'}],
[key_mtime => $mtime],
[key_path => $filepath],
[key_base64 => $key->{'base64'}],
[key_encrypted => $encrypted ? "yes" : "no"],
]
);
HEXIT('OK', value => $key);

@ -0,0 +1,61 @@
#! /usr/bin/env perl
# 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;
use OVH::Bastion::Plugin qw( :DEFAULT help );
my $remainingOptions = OVH::Bastion::Plugin::begin(
argv => \@ARGV,
header => "Remove a bastion group egress key",
options => {
"group=s" => \my $group,
"id=s" => \my $id,
},
helptext => <<"EOF",
Remove a bastion group egress key
Usage: --osh SCRIPT_NAME <--group GROUP> <--id ID>
--group GROUP Name of the group to delete the egress key from
--id ID Specify the key ID to delete, you can get it with groupInfo
EOF
);
#
# code
#
my $fnret;
if (!$id || !$group) {
help();
osh_exit 'ERR_MISSING_PARAMETER', "Missing the --group or --id parameter";
}
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => "key");
$fnret or osh_exit($fnret);
# get returned untainted value
$group = $fnret->value->{'group'};
my $shortGroup = $fnret->value->{'shortGroup'};
if (!OVH::Bastion::is_group_owner(account => $self, group => $shortGroup, superowner => 1)) {
osh_exit 'ERR_NOT_GROUP_OWNER', "You must be an owner to delete an egress group key";
}
$fnret = OVH::Bastion::get_group_keys(group => $group);
my @matchingKeys = grep { $fnret->value->{'keys'}{$_}{'id'} eq $id } @{$fnret->value->{'sortedKeys'} || []};
if (!@matchingKeys) {
osh_exit 'ERR_INVALID_PARAMETER', "Couldn't find any key with the ID you specified ($id) in group $shortGroup";
}
my @command = qw{ sudo -n -u keykeeper -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-groupDelEgressKey';
push @command, "--group", $group, "--id", $id;
osh_exit(OVH::Bastion::helper(cmd => \@command));

@ -0,0 +1,10 @@
{
"interactive": [
"groupDelEgressKey" , {"ac" : ["--group"]},
"groupDelEgressKey --group" , {"ac" : ["<GROUP>"]},
"groupDelEgressKey --group \\S+" , {"ac" : ["--id"]},
"groupDelEgressKey --group \\S+ --id" , {"pr" : ["<KEYID>"]},
"groupDelEgressKey --group \\S+ --id \\d+", {"pr" : ["<enter>"]}
],
"master_only": true
}

@ -0,0 +1,72 @@
#! /usr/bin/env perl
# vim: set filetype=perl ts=4 sw=4 sts=4 et:
use common::sense;
use Term::ReadKey;
use File::Basename;
use lib dirname(__FILE__) . '/../../../lib/perl';
use OVH::Result;
use OVH::Bastion;
use OVH::Bastion::Plugin qw( :DEFAULT );
use OVH::Bastion::Plugin::generateEgressKey;
my $remainingOptions = OVH::Bastion::Plugin::begin(
argv => \@ARGV,
header => "generating a new key pair for a group",
options => {
"group=s" => \my $group,
"algo=s" => \my $algo,
"size=i" => \my $size,
"encrypted" => \my $encrypted,
},
help => \&OVH::Bastion::Plugin::generateEgressKey::help,
);
#
# code
#
my $fnret;
$fnret = OVH::Bastion::Plugin::generateEgressKey::preconditions(
context => 'group',
self => $self,
group => $group,
algo => $algo,
size => $size
);
if ($fnret->err eq 'ERR_MISSING_PARAMETER') {
OVH::Bastion::Plugin::generateEgressKey::help();
osh_exit(R('ERR_MISSING_PARAMETER', msg => "Missing the 'algo', 'size' or 'group' parameter'"));
}
$fnret or osh_exit $fnret;
my ($shortGroup, $keyhome);
($group, $algo, $size, $shortGroup, $keyhome) = @{$fnret->value}{qw{ group algo size shortGroup keyhome }};
my $passphrase = ''; # empty by default
if ($encrypted) {
$fnret = OVH::Bastion::Plugin::generateEgressKey::ask_passphrase();
$fnret or osh_exit $fnret;
$passphrase = $fnret->value;
}
my @command = qw{ sudo -n -u root -- /usr/bin/env perl -T };
push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-groupGenerateEgressKey';
push @command, '--group', $group;
push @command, '--algo', $algo;
push @command, '--size', $size;
push @command, '--encrypted' if $encrypted;
$fnret = OVH::Bastion::helper(cmd => \@command, stdin_str => $passphrase);
$fnret or osh_exit $fnret;
my $key = $fnret->value;
$fnret = OVH::Bastion::get_bastion_ips();
$fnret or osh_exit $fnret;
$key->{'prefix'} = 'from="' . join(',', @{$fnret->value}) . '"';
OVH::Bastion::print_public_key(key => $key);
osh_ok($key);

@ -0,0 +1,14 @@
{
"interactive": [
"groupGenerateEgressKey" , {"ac" : ["--group"]},
"groupGenerateEgressKey --group" , {"ac" : ["<GROUP>"]},
"groupGenerateEgressKey --group \\S+" , {"ac" : ["--algo"]},
"groupGenerateEgressKey --group \\S+ --algo" , {"ac" : ["rsa", "ecdsa", "ed25519"]},
"groupGenerateEgressKey --group \\S+ --algo \\S+" , {"ac" : ["--size"]},
"groupGenerateEgressKey --group \\S+ --algo \\S+ --size" , {"pr" : ["<SIZE>"]},
"groupGenerateEgressKey --group \\S+ --algo \\S+ --size \\d+" , {"ac" : ["<enter>", "--encrypted"]},
"groupGenerateEgressKey --group \\S+ --algo \\S+ --size \\d+ --encrypted", {"pr" : ["<enter>"]}
],
"master_only": true,
"terminal_mode" : "raw"
}

@ -51,7 +51,7 @@ my $shortGroup = $fnret->value->{'shortGroup'};
$fnret = OVH::Bastion::is_group_owner(account => $self, group => $shortGroup, superowner => 1);
if (!$fnret) {
osh_exit 'ERR_NOT_GROUP_OWNER', "You must be an owner to delete an egress group key";
osh_exit 'ERR_NOT_GROUP_OWNER', "You must be an owner to modify this group";
}
if (defined $mfaRequired && !grep { $mfaRequired eq $_ } qw{ password totp any none }) {

@ -8,107 +8,43 @@ use lib dirname(__FILE__) . '/../../../lib/perl';
use OVH::Result;
use OVH::Bastion;
use OVH::Bastion::Plugin qw( :DEFAULT );
use OVH::Bastion::Plugin::generateEgressKey;
my ($algo, $size, $encrypted);
my $remainingOptions = OVH::Bastion::Plugin::begin(
argv => \@ARGV,
header => "generating a new key pair for your account",
options => {
"algo=s" => \$algo,
"size=i" => \$size,
"encrypted" => \$encrypted,
"algo=s" => \my $algo,
"size=i" => \my $size,
"encrypted" => \my $encrypted,
},
help => \&help,
help => \&OVH::Bastion::Plugin::generateEgressKey::help,
);
sub help {
require Term::ANSIColor;
my $fnret = OVH::Bastion::get_supported_ssh_algorithms_list(way => 'egress');
my @algoList = @{$fnret->value};
my $algos = Term::ANSIColor::colored(uc join(' ', @algoList), 'green');
my $helpAlgoSize = '--algo rsa --size 4096';
if (grep { $_ eq 'ecdsa' } @algoList) {
$helpAlgoSize = '--algo ecdsa --size 521';
}
if (grep { $_ eq 'ed25519' } @algoList) {
$helpAlgoSize = '--algo ed25519';
}
osh_info <<"EOF";
Create a new egress public + private key pair. The private key will stay on your account on this bastion.
Usage: --osh $scriptName $helpAlgoSize [--encrypted]
--algo ALGO Specifies the algo of the key, either rsa, ecdsa or ed25519.
--size SIZE Specifies the size of the key to be generated.
For RSA, choose between 2048 and 8192 (4096 is good).
For ECDSA, choose either 256, 384 or 521.
For ED25519, size is always 256.
--encrypted if specified, a passphrase will be prompted for the new key
With the policy and SSH version on this bastion,
the following algorithms are supported: $algos
algo size strength speed compatibility
------- ---- ---------- -------- -----------------------
RSA 4096 good slow works everywhere
ECDSA 521 strong fast debian7+ (OpenSSH 5.7+)
ED25519 256 verystrong veryfast debian8+ (OpenSSH 6.5+)
EOF
return 0;
}
#
# code
#
my $fnret;
if (!$algo) {
help();
osh_exit 'ERR_MISSING_PARAMETER', "Parameter 'algo' is missing";
}
# check if algo is supported by system
$fnret = OVH::Bastion::get_supported_ssh_algorithms_list(way => 'egress');
my @algoList = @{$fnret->value};
my $ok = 0;
foreach (@algoList) {
$algo =~ /^\Q$_\E/ and $ok = 1;
}
if (not $ok) {
osh_debug($algo);
osh_debug(join(' ', @algoList));
osh_exit 'ERR_INVALID_ALGORITHM', "Only the following list of algorithms is allowed: " . join(' ', @algoList);
$fnret = OVH::Bastion::Plugin::generateEgressKey::preconditions(
context => 'account',
account => $self,
algo => $algo,
size => $size
);
if ($fnret->err eq 'ERR_MISSING_PARAMETER') {
OVH::Bastion::Plugin::generateEgressKey::help();
osh_exit(R('ERR_MISSING_PARAMETER', msg => "Missing the 'algo' or 'size' parameter'"));
}
$size = 256 if (not $size and $algo eq 'ed25519');
$fnret = OVH::Bastion::is_allowed_algo_and_size(algo => $algo, size => $size, way => 'egress');
$fnret or osh_exit $fnret;
$fnret = OVH::Bastion::config('bastionName');
$fnret or osh_exit $fnret;
my $bastionName = $fnret->value;
($algo, $size) = @{$fnret->value}{qw{ algo size }};
my $passphrase = ''; # empty by default
if ($encrypted) {
print "Please enter a passphrase for your new personal bastion key (not echoed): ";
ReadMode('noecho');
chomp(my $pass1 = <STDIN>);
if (length($pass1) < 5) {
# ssh-keygen will refuse
print "\n";
osh_exit 'ERR_PASSPHRASE_TOO_SHORT', "Passphrase needs to be at least 5 chars";
}
print "\nPlease enter it again: ";
chomp(my $pass2 = <STDIN>);
print "\n";
ReadMode('restore');
if ($pass1 ne $pass2) {
osh_exit 'ERR_PASSPHRASE_MISMATCH', "Passphrases don't match, please try again";
}
$passphrase = $pass1;
$fnret = OVH::Bastion::Plugin::generateEgressKey::ask_passphrase();
$fnret or osh_exit $fnret;
$passphrase = $fnret->value;
}
osh_info "Generating your key, this might take a while...";
@ -125,6 +61,7 @@ $fnret or osh_exit $fnret;
osh_info "You new key pair has been generated:\n";
$fnret = OVH::Bastion::get_ssh_pub_key_info(file => $fnret->value->{'file'} . ".pub", way => "egress");
$fnret or osh_exit $fnret;
my $key = $fnret->value;
$fnret = OVH::Bastion::get_bastion_ips();

@ -13,6 +13,12 @@ SUPEROWNERS, %%GROUP%-owner ALL=(root) NOPASSWD: /usr/bin/env perl -
# as an owner, we can generate an egress password for the group
SUPEROWNERS, %%GROUP%-owner ALL=(%GROUP%) NOPASSWD: /usr/bin/env perl -T %BASEPATH%/bin/helper/osh-groupGeneratePassword --group %GROUP% *
# as an owner, we can generate an egress key for the group
SUPEROWNERS, %%GROUP%-owner ALL=(root) NOPASSWD: /usr/bin/env perl -T %BASEPATH%/bin/helper/osh-groupGenerateEgressKey --group %GROUP% *
# as an owner, we can delete an egress key of the group
SUPEROWNERS, %%GROUP%-owner ALL=(keykeeper) NOPASSWD: /usr/bin/env perl -T %BASEPATH%/bin/helper/osh-groupDelEgressKey --group %GROUP% *
# as a gatekeeper, we can grant/revoke membership
SUPEROWNERS, %%GROUP%-gatekeeper ALL=(root) NOPASSWD: /usr/bin/env perl -T %BASEPATH%/bin/helper/osh-groupSetRole --type member --group %GROUP% *
# as a gatekeeper, to be able to symlink in /home/allowkeeper/ACCOUNT the /home/%GROUP%/allowed.ip file

@ -0,0 +1,122 @@
package OVH::Bastion::Plugin::generateEgressKey;
# 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;
use OVH::Bastion::Plugin qw{ :DEFAULT };
sub help {
require Term::ANSIColor;
my $fnret = OVH::Bastion::get_supported_ssh_algorithms_list(way => 'egress');
my @algoList = @{$fnret->value};
my $algos = Term::ANSIColor::colored(uc join(' ', @algoList), 'green');
my $helpAlgoSize = '--algo rsa --size 4096';
if (grep { $_ eq 'ecdsa' } @algoList) {
$helpAlgoSize = '--algo ecdsa --size 521';
}
if (grep { $_ eq 'ed25519' } @algoList) {
$helpAlgoSize = '--algo ed25519';
}
osh_info <<"EOF";
Create a new public + private key pair. The private key will stay on this bastion.
Usage: --osh $scriptName $helpAlgoSize [--encrypted]
--algo ALGO Specifies the algo of the key, either rsa, ecdsa or ed25519.
--size SIZE Specifies the size of the key to be generated.
For RSA, choose between 2048 and 8192 (4096 is good).
For ECDSA, choose either 256, 384 or 521.
For ED25519, size is always 256.
--encrypted if specified, a passphrase will be prompted for the new key
With the policy and SSH version on this bastion,
the following algorithms are supported: $algos
algo size strength speed compatibility
------- ---- ---------- -------- -----------------------
RSA 4096 good slow works everywhere
ECDSA 521 strong fast debian7+ (OpenSSH 5.7+)
ED25519 256 verystrong veryfast debian8+ (OpenSSH 6.5+)
EOF
return 0;
}
sub ask_passphrase {
require Term::ReadKey;
print "Please enter a passphrase for the private key that'll stay on the bastion (not echoed): ";
Term::ReadKey::ReadMode('noecho');
chomp(my $pass1 = <STDIN>);
if (length($pass1) < 5) {
# ssh-keygen will refuse
print "\n";
return R('ERR_PASSPHRASE_TOO_SHORT', msg => "Passphrase needs to be at least 5 chars");
}
print "\nPlease enter it again: ";
chomp(my $pass2 = <STDIN>);
print "\n";
Term::ReadKey::ReadMode('restore');
if ($pass1 ne $pass2) {
return R('ERR_PASSPHRASE_MISMATCH', msg => "Passphrases don't match, please try again");
}
return R('OK', value => $pass1);
}
sub preconditions {
my %params = @_;
my $fnret;
my ($self, $group, $algo, $size, $account, $sudo, $context) = @params{qw{ self group algo size account sudo context}};
if (!$algo || !$context) {
return R('ERR_MISSING_PARAMETER', msg => "Missing argument algo[$algo] or context[$context]");
}
if (!grep { $context eq $_ } qw{ group account }) {
return R('ERR_INVALID_PARAMETER', msg => "Type should be group or account");
}
# check whether algo is supported by system
$fnret = OVH::Bastion::is_allowed_algo_and_size(algo => $algo, size => $size, way => 'egress');
$fnret or return $fnret;
($algo, $size) = @{$fnret->value}{qw{ algo size }}; # untaint
# check preconditions if we're generating a key for a group
if ($context eq 'group') {
if (!$group || !$self) {
return R('ERR_MISSING_PARAMETER', msg => "Missing 'group' or 'self' parameter");
}
$fnret = OVH::Bastion::is_valid_group_and_existing(group => $group, groupType => 'key');
$fnret or return $fnret;
my $keyhome = $fnret->value->{'keyhome'};
my $shortGroup = $fnret->value->{'shortGroup'};
$group = $fnret->value->{'group'};
$fnret = OVH::Bastion::is_group_owner(group => $shortGroup, account => $self, superowner => 1, sudo => $sudo);
if (!$fnret) {
return R('ERR_NOT_GROUP_OWNER', msg => "Sorry, you're not an owner of group $shortGroup, which is needed to manage its egress keys ($fnret)");
}
return R('OK', value => {group => $group, shortGroup => $shortGroup, keyhome => $keyhome, algo => $algo, size => $size, context => $context});
}
elsif ($context eq 'account') {
if (!$account) {
return R('ERR_MISSING_PARAMETER', msg => "Missing 'group' parameter");
}
$fnret = OVH::Bastion::is_bastion_account_valid_and_existing(account => $account);
$fnret or return $fnret;
return R('OK', value => {algo => $algo, size => $size, context => $context});
}
else {
return R('ERR_INTERNAL');
}
}
1;

@ -664,6 +664,7 @@ sub is_allowed_algo_and_size {
}
if ($algo eq 'rsa') {
$algo = 'rsa'; # untaint
$way = ucfirst($way);
$fnret = OVH::Bastion::config("minimum${way}RsaKeySize");
$fnret or return $fnret;
@ -672,14 +673,20 @@ sub is_allowed_algo_and_size {
}
}
elsif ($algo eq 'ecdsa') {
$algo = 'ecdsa'; # untaint
if (not grep { $size eq $_ } qw{ 256 384 521 }) {
return R('KO_KEY_SIZE_INVALID', msg => "For the selected algorithm, valid key sizes are 256, 384, 521");
}
}
elsif ($algo eq 'ed25519' && $size && $size ne '256') {
return R('KO_KEY_SIZE_INVALID', msg => "For the selected algorithm, key size must be 256");
elsif ($algo eq 'ed25519') {
$algo = 'ed25519'; # untaint
if ($size && $size ne '256') {
return R('KO_KEY_SIZE_INVALID', msg => "For the selected algorithm, key size must be 256");
}
$size = 256;
}
return R('OK');
($size) = $size =~ /^(\d+)$/; # untaint
return R('OK', value => {algo => $algo, size => $size});
}
sub is_valid_fingerprint {

@ -104,6 +104,45 @@ EOS
)
# new state: g1[a1(ow,gk,acl,member)]
# create g3 with a3 as owner to test key generation of a group a3 is not an owner of, without getting the early no-owner deny
success 350-groups a0_create_g3_with_a3_as_owner $a0 --osh groupCreate --group $group3 --algo ed25519 --owner $account3
# test egress key generation as an owner
run 350-groups a0_generate_key_g1_fail $a0 --osh groupGenerateEgressKey --group $group1 --algo ed25519
retvalshouldbe 106
json .command null .error_code KO_RESTRICTED_COMMAND .value null
plgfail 350-groups a3_generate_key_g1_fail $a3 --osh groupGenerateEgressKey --group $group1 --algo ed25519
json .command groupGenerateEgressKey .error_code ERR_NOT_GROUP_OWNER
success 350-groups a1_generate_key_g1 $a1 --osh groupGenerateEgressKey --group $group1 --algo ed25519
json .command groupGenerateEgressKey .error_code OK .value.typecode ssh-ed25519
local key1id
local key1fp
key1id=$(get_json | $jq .value.id)
key1fp=$(get_json | $jq .value.fingerprint)
success 350-groups a0_list_group_keys_g1 $a0 --osh groupInfo --group $group1
json .command groupInfo .error_code OK ".value.keys.\"$key1fp\".typecode" ssh-ed25519
run 350-groups a0_del_key_g1 $a0 --osh groupDelEgressKey --group $group1 --id $key1id
retvalshouldbe 106
json .command null .error_code KO_RESTRICTED_COMMAND .value null
plgfail 350-groups a3_del_key_g1 $a3 --osh groupDelEgressKey --group $group1 --id $key1id
json .command groupDelEgressKey .error_code ERR_NOT_GROUP_OWNER
success 350-groups a1_del_key_g1 $a1 --osh groupDelEgressKey --group $group1 --id $key1id
json .command groupDelEgressKey .error_code OK .value.id "$key1id" .value.fingerprint "$key1fp"
unset key1id
unset key1fp
grant groupDelete
script 350-groups a0_delete_g3 $a0 --osh groupDelete --group $group3 '<<<' "$group3"
retvalshouldbe 0
revoke groupDelete
# /egress key generation
# now test all group-* commands from a2 to grant a3 on g1 => should get an early deny
run groupAddOwner a2_fail_to_addowner_a3_on_g1_early_deny_owner_cmd $a2 --osh groupAddOwner --group $group1 --account $account3

Loading…
Cancel
Save