diff --git a/bin/helper/osh-accountModify b/bin/helper/osh-accountModify index e0d5afa..ff5305b 100755 --- a/bin/helper/osh-accountModify +++ b/bin/helper/osh-accountModify @@ -223,7 +223,7 @@ sub _toggle_yes_no { foreach my $tuple (@modify) { my ($key, $value) = $tuple =~ /^([a-zA-Z0-9-]+)=([a-zA-Z0-9-]+)$/; - next if (!$key || !$value); + next if (!$key || !defined $value); my $jsonkey = $key; $jsonkey =~ s/-/_/g; @@ -336,6 +336,24 @@ foreach my $tuple (@modify) { } } } + elsif ($key eq 'max-inactive-days') { + osh_info "Changing the account expiration policy..."; + if ($value !~ /^(?:\d+|-1)$/) { + osh_warn "Invalid parameter '$value', skipping"; + $result{$jsonkey} = R('ERR_INVALID_PARAMETER'); + } + else { + my %todo = ($value >= 0 ? (value => $value) : (delete => 1)); + $fnret = OVH::Bastion::account_config(account => $account, %todo, %{OVH::Bastion::OPT_ACCOUNT_MAX_INACTIVE_DAYS()}); + $result{$jsonkey} = $fnret; + if ($fnret) { + osh_info "... modification done"; + } + else { + osh_warn "... error while setting the account expiration policy: " . $fnret->msg; + } + } + } } HEXIT('OK', value => \%result); diff --git a/bin/plugin/restricted/accountInfo b/bin/plugin/restricted/accountInfo index b653ff7..aafb993 100755 --- a/bin/plugin/restricted/accountInfo +++ b/bin/plugin/restricted/accountInfo @@ -274,6 +274,17 @@ if (OVH::Bastion::is_auditor(account => $self)) { $ret{'idle_ignore'} = OVH::Bastion::account_config(account => $account, key => OVH::Bastion::OPT_ACCOUNT_IDLE_IGNORE, public => 1) ? 1 : 0; osh_info "\n- Account is immune to idle counter-measures: " . ($ret{'idle_ignore'} ? colored('yes', 'green') : colored('no', 'blue')); + $ret{'max_inactive_days'} = OVH::Bastion::account_config(account => $account, %{OVH::Bastion::OPT_ACCOUNT_MAX_INACTIVE_DAYS()})->value; + if (!defined $ret{'max_inactive_days'}) { + osh_info "- Maximum number of days of inactivity before account is disabled: (default)"; + } + elsif ($ret{'max_inactive_days'} == 0) { + osh_info "- Maximum number of days of inactivity before account is disabled: never"; + } + else { + osh_info "- Maximum number of days of inactivity before account is disabled: " . $ret{'max_inactive_days'}; + } + my @command = qw{ sudo -n -u root -- /usr/bin/env perl -T }; push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountGetPasswordInfo'; push @command, '--account', $account; diff --git a/bin/plugin/restricted/accountModify b/bin/plugin/restricted/accountModify index c7259f6..a823299 100755 --- a/bin/plugin/restricted/accountModify +++ b/bin/plugin/restricted/accountModify @@ -21,6 +21,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "egress-strict-host-key-checking=s" => \$modify{'egress-strict-host-key-checking'}, "personal-egress-mfa-required=s" => \$modify{'personal-egress-mfa-required'}, "idle-ignore=s" => \$modify{'idle-ignore'}, + "max-inactive-days=i" => \$modify{'max-inactive-days'}, "osh-only=s" => \$modify{'osh-only'}, }, helptext => <<'EOF', @@ -49,6 +50,9 @@ Usage: --osh SCRIPT_NAME --account ACCOUNT [--option value [--option value [...] using the personal keys of the account, POLICY can be 'password', 'totp', 'any' or 'none' --always-active yes|no Set or unset the account as always active (i.e. disable the check of the 'active' status on this account) --idle-ignore yes|no If enabled, this account is immune to the idleLockTimeout and idleKillTimeout bastion-wide policy + --max-inactive-days DAYS Set account expiration policy, overriding the global bastion configuration 'accountMaxInactiveDays'. + Setting this option to zero disables account expiration. Setting this option to -1 removes this account + expiration policy, i.e. the global bastion setting will apply. --osh-only yes|no If enabled, this account can only use ``--osh`` commands, and can't connect anywhere through the bastion EOF ); @@ -91,12 +95,16 @@ if ($modify{'personal-egress-mfa-required'} && !grep { $modify{'personal-egress- help(); osh_exit 'ERR_INVALID_PARAMETER', "Expected option 'password', 'totp', 'any', 'none' to --personal-egress-mfa-required"; } +if ($modify{'max-inactive-days'} && $modify{'max-inactive-days'} !~ /^(?:\d+|-1)$/) { + help(); + osh_exit "ERR_INVALID_PARAMETER", "Expected -1, 0, or a strictly positive number of days as parameter to --max-inactive-days"; +} my @command = qw{ sudo -n -u root -- /usr/bin/env perl -T }; push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-accountModify'; push @command, '--account', $account; foreach my $key (keys %modify) { - push @command, '--modify', $key . '=' . $modify{$key} if $modify{$key}; + push @command, '--modify', $key . '=' . $modify{$key} if defined $modify{$key}; } osh_exit OVH::Bastion::helper(cmd => \@command); diff --git a/doc/sphinx/plugins/restricted/accountModify.rst b/doc/sphinx/plugins/restricted/accountModify.rst index ef621a4..47538a1 100644 --- a/doc/sphinx/plugins/restricted/accountModify.rst +++ b/doc/sphinx/plugins/restricted/accountModify.rst @@ -59,6 +59,12 @@ Modify an account configuration If enabled, this account is immune to the idleLockTimeout and idleKillTimeout bastion-wide policy +.. option:: --max-inactive-days DAYS + + Set account expiration policy, overriding the global bastion configuration 'accountMaxInactiveDays'. + + Setting this option to zero disables account expiration. Setting this option to -1 removes this account + expiration policy, i.e. the global bastion setting will apply. .. option:: --osh-only yes|no If enabled, this account can only use ``--osh`` commands, and can't connect anywhere through the bastion diff --git a/lib/perl/OVH/Bastion.pm b/lib/perl/OVH/Bastion.pm index 834cec5..74ecd40 100644 --- a/lib/perl/OVH/Bastion.pm +++ b/lib/perl/OVH/Bastion.pm @@ -116,6 +116,8 @@ use constant { OPT_ACCOUNT_ALWAYS_ACTIVE => 'always_active', OPT_ACCOUNT_IDLE_IGNORE => 'idle_ignore', OPT_ACCOUNT_OSH_ONLY => 'osh_only', + + OPT_ACCOUNT_MAX_INACTIVE_DAYS => {key => 'max_inactive_days', public => 1}, }; ########### @@ -183,6 +185,12 @@ sub is_account_nonexpired { $accountMaxInactiveDays = $fnret->value; } + # some accounts might have a specific configuration overriding the global one + $fnret = OVH::Bastion::account_config(account => $sysaccount, %{OVH::Bastion::OPT_ACCOUNT_MAX_INACTIVE_DAYS()}); + if ($fnret) { + $accountMaxInactiveDays = $fnret->value; + } + my $isFirstLogin; my $lastlog; my $filepath = "/home/$sysaccount/lastlog" . ($remoteaccount ? "_$remoteaccount" : ""); diff --git a/tests/functional/tests.d/325-accountinfo.sh b/tests/functional/tests.d/325-accountinfo.sh index cf028cc..1eb9996 100644 --- a/tests/functional/tests.d/325-accountinfo.sh +++ b/tests/functional/tests.d/325-accountinfo.sh @@ -41,6 +41,7 @@ testsuite_accountinfo() json .value.ingress_piv_enforced 0 .value.always_active 1 .value.creation_information.by "$account0" json .value.creation_information.comment "this is a comment" json .value.already_seen_before 0 .value.last_activity null + json .value.max_inactive_days null # a2 connects, which will update already_seen_before success 325-accountinfo a2_connects $a2 --osh info @@ -51,6 +52,31 @@ testsuite_accountinfo() json .value.already_seen_before 1 contain "Last seen on" + grant accountModify + + # a0 changes a2 expiration policy + success 325-accountinfo a0_accountmodify_a2_expi_15 $a0 --osh accountModify --account $account2 --max-inactive-days 15 + + # a1 should see the updated field + success 325-accountinfo a1_accountinfo_a2_inactive_days $a1 --osh accountInfo --account $account2 + json .value.max_inactive_days 15 + + # a0 changes a2 expiration policy + success 325-accountinfo a0_accountmodify_a2_expi_disabled $a0 --osh accountModify --account $account2 --max-inactive-days 0 + + # a1 should see the updated field + success 325-accountinfo a1_accountinfo_a2_inactive_days_disabled $a1 --osh accountInfo --account $account2 + json .value.max_inactive_days 0 + + # a0 changes a2 expiration policy + success 325-accountinfo a0_accountmodify_a2_expi_default $a0 --osh accountModify --account $account2 --max-inactive-days -1 + + # a1 should see the updated field + success 325-accountinfo a1_accountinfo_a2_inactive_days_default $a1 --osh accountInfo --account $account2 + json .value.max_inactive_days null + + revoke accountModify + # delete account1 & account2 grant accountDelete success 325-accountinfo a0_delete_a1 $a0 --osh accountDelete --account $account1 --no-confirm