feat: add try-personal-keys policy for groups (#2)

Signed-off-by: Jonah Zürcher <jonah.zuercher@adfinis.com>
pull/597/head
Jonah 5 months ago committed by GitHub
parent bdc360b421
commit 107a6592a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -18,7 +18,7 @@ use OVH::Result;
# Fetch command options
my $fnret;
my ($result, @optwarns);
my ($group, $mfaRequired, $ttl, $idleLockTimeout, $idleKillTimeout);
my ($group, $mfaRequired, $ttl, $idleLockTimeout, $idleKillTimeout, $tryPersonalKeys);
eval {
local $SIG{__WARN__} = sub { push @optwarns, shift };
$result = GetOptions(
@ -27,6 +27,7 @@ eval {
"guest-ttl-limit=i" => \$ttl,
"idle-lock-timeout=i" => \$idleLockTimeout,
"idle-kill-timeout=i" => \$idleKillTimeout,
"try-personal-keys=s" => \$tryPersonalKeys,
);
};
if ($@) { die $@ }
@ -42,9 +43,11 @@ if (!$group) {
HEXIT('ERR_MISSING_PARAMETER', msg => "Missing argument 'group'");
}
if (!$mfaRequired && !defined $ttl && !defined $idleLockTimeout && !defined $idleKillTimeout) {
if (!$mfaRequired && !defined $ttl && !defined $idleLockTimeout && !defined $idleKillTimeout && !$tryPersonalKeys) {
HEXIT('ERR_MISSING_PARAMETER',
msg => "Missing argument 'mfa-required', 'guest-ttl-limit', 'idle-lock-timeout' or 'idle-kill-timeout'");
msg =>
"Missing argument 'mfa-required', 'guest-ttl-limit', 'idle-lock-timeout', 'idle-kill-timeout' or 'try-personal-keys'"
);
}
#<HEADER
@ -91,6 +94,28 @@ if (defined $mfaRequired) {
}
}
if (defined $tryPersonalKeys) {
osh_info "Modifying try-personal-keys policy of group...";
if (grep { $tryPersonalKeys eq $_ } qw{ yes no }) {
$fnret = OVH::Bastion::group_config(
group => $group,
%{OVH::Bastion::OPT_GROUP_TRY_PERSONAL_KEYS()},
value => ($tryPersonalKeys eq 'yes' ? 1 : 0)
);
if ($fnret) {
osh_info "... done, policy is now: $tryPersonalKeys";
}
else {
osh_warn "... error while changing try-personal-keys policy (" . $fnret->msg . ")";
}
$result{'try_personal_keys'} = $fnret;
}
else {
osh_warn "... invalid option '$tryPersonalKeys'";
$result{'try_personal_keys'} = R('ERR_INVALID_PARAMETER');
}
}
my %idleTimeout = (
lock => {
name => "idle lock timeout",

@ -17,11 +17,12 @@ my $remainingOptions = OVH::Bastion::Plugin::begin(
"idle-lock-timeout=s" => \my $idleLockTimeout,
"idle-kill-timeout=s" => \my $idleKillTimeout,
"guest-ttl-limit=s" => \my $ttl,
"try-personal-keys=s" => \my $tryPersonalKeys,
},
helptext => <<'EOF',
Modify the configuration of a group
Usage: --osh SCRIPT_NAME --group GROUP [--mfa-required password|totp|any|none] [--guest-ttl-limit DURATION]
Usage: --osh SCRIPT_NAME --group GROUP [--mfa-required password|totp|any|none] [--guest-ttl-limit DURATION] [--try-personal-keys yes|no]
--group GROUP Name of the group to modify
--mfa-required password|totp|any|none Enforce UNIX password requirement, or TOTP requirement, or any MFA requirement, when connecting to a server of the group
@ -31,6 +32,8 @@ Usage: --osh SCRIPT_NAME --group GROUP [--mfa-required password|totp|any|none] [
this group. If set to -1, remove this group override and use the global setting instead.
--guest-ttl-limit DURATION This group will enforce TTL setting, on guest access creation, to be set, and not to a higher value than DURATION,
set to zero to allow guest accesses creation without any TTL set (default)
--try-personal-keys yes|no When a user accesses a server through his group permission, his personal access keys will also be added
to the connection attempt (default: no)
Note that `--idle-lock-timeout` and `--idle-kill-timeout` will NOT be applied for catch-all groups (having 0.0.0.0/0 in their server list).
@ -48,7 +51,12 @@ if (!$group) {
help();
osh_exit 'ERR_MISSING_PARAMETER', "Missing mandatory parameter 'group'";
}
if (!$mfaRequired && !defined $ttl && !defined $idleLockTimeout && !defined $idleKillTimeout) {
if ( !$mfaRequired
&& !defined $ttl
&& !defined $idleLockTimeout
&& !defined $idleKillTimeout
&& !defined $tryPersonalKeys)
{
help();
osh_exit 'ERR_MISSING_PARAMETER', "Nothing to modify";
}
@ -81,6 +89,11 @@ if (defined $mfaRequired && !grep { $mfaRequired eq $_ } qw{ password totp any n
osh_exit 'ERR_INVALID_PARAMETER', "Expected 'password', 'totp', 'any' or 'none' as parameter to --mfa-required";
}
if (defined $tryPersonalKeys && !grep { $tryPersonalKeys eq $_ } qw{ yes no }) {
help();
osh_exit 'ERR_INVALID_PARAMETER', "Expected 'yes' or 'no' as parameter to --try-personal-keys";
}
my @command = qw{ sudo -n -u };
push @command, $group;
push @command, qw{ -- /usr/bin/env perl -T };
@ -90,5 +103,6 @@ push @command, '--mfa-required', $mfaRequired if $mfaRequired;
push @command, '--guest-ttl-limit', $ttl if defined $ttl;
push @command, '--idle-lock-timeout', $idleLockTimeout if defined $idleLockTimeout;
push @command, '--idle-kill-timeout', $idleKillTimeout if defined $idleKillTimeout;
push @command, '--try-personal-keys', $tryPersonalKeys if defined $tryPersonalKeys;
osh_exit OVH::Bastion::helper(cmd => \@command);

@ -205,6 +205,11 @@ foreach my $groupData (@groups) {
if ($fnret && defined $fnret->value && $fnret->value =~ /^-?\d+$/) {
$ret{'idle_lock_timeout'} = $fnret->value;
}
$fnret = OVH::Bastion::group_config(group => $group, %{OVH::Bastion::OPT_GROUP_TRY_PERSONAL_KEYS()});
if ($fnret && defined $fnret->value) {
$ret{'try_personal_keys'} = ($fnret->value eq '1' ? 'yes' : 'no');
}
}
# group egress keys if we've been asked those
@ -302,6 +307,17 @@ sub print_group_info {
osh_warn "Specific idle kill timeout: idle sessions on servers of this group will $action";
}
if ($ret{'try_personal_keys'}) {
osh_info ' ';
if ($ret{'try_personal_keys'} eq 'yes') {
osh_info
"Personal keys: When group members access servers from this group, their personal egress keys will also be tried.";
}
else {
osh_info "Personal keys: No personal egress keys will be used.";
}
}
if ($withKeys) {
osh_info ' ';
if (!%{$ret{'keys'}}) {

@ -220,7 +220,8 @@ if ($header) {
1) Check the remote account's authorized_keys on $ip, did you add the proper key there? (personal key or group key)
2) Did you tell the bastion you added a key to the remote server, so it knows it has to use it? See the actually used keys just above. If you didn't, do it with selfAddPersonalAccess or groupAddServer.
3) Check the from="" part of the remote account's authorized_keys' keyline. Are all the bastion IPs present? Master and slave(s)? See groupInfo or selfListEgressKeys to get the proper keyline to copy/paste.
4) Did you check the 3 above points carefully? Really? Because if you did, you wouldn't be reading this 4th bullet point, as your problem would already be fixed ;)
4) Are you trying to access a remote server through a group permission but the remote server expects your personal access key? Ask your group owner to enable the 'try-personal-keys' policy.
5) Did you check the 4 above points carefully? Really? Because if you did, you wouldn't be reading this 5th bullet point, as your problem would already be fixed ;)
EOS
);
}

@ -132,6 +132,7 @@ use constant {
OPT_GROUP_IDLE_LOCK_TIMEOUT => {key => 'idle_lock_timeout'},
OPT_GROUP_IDLE_KILL_TIMEOUT => {key => 'idle_kill_timeout'},
OPT_GROUP_TRY_PERSONAL_KEYS => {key => 'try_personal_keys'},
};
###########

@ -859,6 +859,16 @@ sub is_access_granted {
# normal member case, just reuse $grantedGroup
osh_debug("is_access_granted: adding grantedGroup to grants because is member");
push @grants, {type => 'group-member', group => $shortGroup, %{$grantedGroup->value}};
# check if group has the "try-personal-keys" option enabled, and if so, add personal keys too
my $tryPersonalKeysConfig =
OVH::Bastion::group_config(group => $group, %{OVH::Bastion::OPT_GROUP_TRY_PERSONAL_KEYS()});
if ($tryPersonalKeysConfig && $tryPersonalKeysConfig->value && $tryPersonalKeysConfig->value eq '1') {
osh_debug(
"is_access_granted: try-personal-keys is enabled for group $shortGroup, adding personal access");
push @grants, {type => 'personal-via-group', group => $shortGroup, %{$grantedGroup->value}};
}
}
elsif (OVH::Bastion::is_group_guest(group => $shortGroup, account => $account, sudo => $params{'sudo'})) {
@ -871,6 +881,16 @@ sub is_access_granted {
if ($grantedGuest && $grantedGroup) {
push @grants, {type => 'group-guest', group => $shortGroup, %{$grantedGuest->value}};
osh_debug("is_access_granted: adding grantedGuest to grants because is guest and group has access");
# check if group has the "try-personal-keys" option enabled, and if so, add personal keys too
my $tryPersonalKeysConfig =
OVH::Bastion::group_config(group => $group, %{OVH::Bastion::OPT_GROUP_TRY_PERSONAL_KEYS()});
if ($tryPersonalKeysConfig && $tryPersonalKeysConfig->value && $tryPersonalKeysConfig->value eq '1') {
osh_debug(
"is_access_granted: try-personal-keys is enabled for group $shortGroup, adding personal access"
);
push @grants, {type => 'personal-via-group', group => $shortGroup, %{$grantedGroup->value}};
}
}
# special legacy case; we also check if account has a legacy access for ip AND that the group ALSO has access to this ip
@ -913,6 +933,22 @@ sub is_access_granted {
$data{'mfa'} =
OVH::Bastion::account_config(key => "personal_egress_mfa_required", account => $sysaccount);
}
elsif ($access->{'type'} eq 'personal-via-group') {
# This is personal access triggered by try-personal-keys group option
# Use personal keys but group MFA and timeout settings
$data{'keys'} = OVH::Bastion::get_personal_account_keys(
account => $sysaccount,
listOnly => $listOnly,
noexec => $noexec,
forceKey => $access->{'forceKey'}
);
# Use group MFA and timeout settings since access is via group
$data{'mfa'} = OVH::Bastion::group_config(key => "mfa_required", group => $access->{'group'});
$data{'idle_lock_timeout'} = OVH::Bastion::group_config(%{OVH::Bastion::OPT_GROUP_IDLE_LOCK_TIMEOUT()},
group => $access->{'group'});
$data{'idle_kill_timeout'} = OVH::Bastion::group_config(%{OVH::Bastion::OPT_GROUP_IDLE_KILL_TIMEOUT()},
group => $access->{'group'});
}
else {
# unknown access type? no key!
warn_syslog("Unknown access type '" . $access->{'type'} . "' found, ignoring");

@ -1088,6 +1088,34 @@ EOS
success guest_ttl_limit $a1 --osh groupModify --group $group1 --guest-ttl-limit 0
json .command groupModify .error_code OK
# try-personal-keys tests
# test invalid parameter values for try-personal-keys
plgfail invalid_try_personal_keys_value $a1 --osh groupModify --group $group1 --try-personal-keys invalid
json .error_code ERR_INVALID_PARAMETER
# enable try-personal-keys
success enable_try_personal_keys $a1 --osh groupModify --group $group1 --try-personal-keys yes
json .error_code OK .command groupModify .value.try_personal_keys.error_code OK
# verify the setting is applied in groupInfo
success check_try_personal_keys_enabled $a1 --osh groupInfo --group $group1
contain "personal egress keys will also be tried"
json .error_code OK .command groupInfo .value.try_personal_keys yes
# disable try-personal-keys
success disable_try_personal_keys $a1 --osh groupModify --group $group1 --try-personal-keys no
json .error_code OK .command groupModify .value.try_personal_keys.error_code OK
# verify the setting is applied in groupInfo
success check_try_personal_keys_disabled $a1 --osh groupInfo --group $group1
contain "No personal egress keys will be used"
json .error_code OK .command groupInfo .value.try_personal_keys no
# non-owner cannot modify try-personal-keys
run non_owner_modify_try_personal_keys $a2 --osh groupModify --group $group1 --try-personal-keys yes
retvalshouldbe 106
json .error_code KO_RESTRICTED_COMMAND
# if we're just counting the number of tests, don't sleep
[ "$COUNTONLY" != 1 ] && sleep 1
@ -1147,6 +1175,60 @@ EOS
nocontain "$group1"
contain "personal access"
# try-personal-keys tests
# First, make account2 a member instead of just a guest for proper testing
success add_a2_as_member_for_try_personal_keys $a1 --osh groupAddMember --group $group1 --account $account2
json .error_code OK .command groupAddMember
# Without try-personal-keys enabled - should only show group access
run a2_group_access_only $a2 127.0.0.11 --user testuser
retvalshouldbe 255
contain "allowed ... log on"
contain "group-member of $group1"
nocontain "personal-via-group"
# Enable try-personal-keys and test access
success enable_try_personal_keys_for_access $a1 --osh groupModify --group $group1 --try-personal-keys yes
json .error_code OK .command groupModify
# Now connection should show both group and personal access
run a2_group_and_personal_access $a2 127.0.0.11 --user testuser
retvalshouldbe 255
contain "allowed ... log on"
contain "group-member of $group1"
contain "personal-via-group"
# Disable try-personal-keys again
success disable_try_personal_keys_for_access $a1 --osh groupModify --group $group1 --try-personal-keys no
json .error_code OK .command groupModify
# Should only show group access again
run a2_group_access_only_again $a2 127.0.0.11 --user testuser
retvalshouldbe 255
contain "allowed ... log on"
contain "group-member of $group1"
nocontain "personal-via-group"
# Enable try-personal-keys again
success enable_try_personal_keys_again $a1 --osh groupModify --group $group1 --try-personal-keys yes
json .error_code OK .command groupModify
# Remove account2 from group members (this will also remove guest access)
success remove_a2_from_members $a1 --osh groupDelMember --group $group1 --account $account2
json .error_code OK .command groupDelMember
# Add account2 back as guest to test guest access only
success add_a2_as_guest_after_removal $a1 --osh groupAddGuestAccess --group $group1 --account $account2 --host 127.0.0.10 --user testuser --port 22
json .error_code OK .command groupAddGuestAccess
# Account2 is now guest but can still use personal access keys to connect
run a2_guest_access_only_after_member_removal $a2 127.0.0.10 --user testuser
retvalshouldbe 255
contain "allowed ... log on"
contain "group-guest of $group1"
nocontain "group-member"
contain "personal-via-group"
# group1: a1(owner,aclkeeper,gatekeeper,member) a2(guest(127.0.0.10)) servers(127.0.0.10,127.0.0.11)
# account1: perso(account1@127.0.0.11:22)
plgfail transmitownership_noright1 $a3 --osh groupTransmitOwnership --group $group1 --account $account1

Loading…
Cancel
Save