diff --git a/bin/helper/osh-accountMFAResetTOTP b/bin/helper/osh-accountMFAResetTOTP index 16c22136d..d15478bbf 100755 --- a/bin/helper/osh-accountMFAResetTOTP +++ b/bin/helper/osh-accountMFAResetTOTP @@ -77,15 +77,31 @@ if (OVH::Bastion::is_admin(account => $account, sudo => 1) && !OVH::Bastion::is_ HEXIT('ERR_SECURITY_VIOLATION', msg => "You can't reset the TOTP of an admin without being admin yourself"); } +my $TOTPProvider = OVH::Bastion::config("TOTPProvider")->value; +if ($TOTPProvider eq 'none') { + HEXIT('ERR_CONFIGURATION_ERROR', msg => "TOTP Provider has not been set, please report to your sysadmin"); +} +elsif ($TOTPProvider eq 'google-authenticator') { + + # for google-authenticator, attempt remove the .otp file (non-fatal) + if (!unlink($home . '/' . OVH::Bastion::TOTP_GAUTH_FILENAME)) { + warn_syslog("Couldn't remove the TOTP file ($!), this is not fatal, continuing anyway"); + } +} +elsif ($TOTPProvider eq 'duo') { + + # duo doesn't need any user-specific local cleanup +} +else { + # unknown provider, this shouldn't happen + HEXIT('ERR_CONFIGURATION_ERROR', msg => "An unknown TOTP provider has been provided, please check with your sysadmin."); +} + +# remove the user from the TOTP configured group if (OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_CONFIGURED_GROUP)) { $fnret = OVH::Bastion::sys_delmemberfromgroup(user => $account, group => OVH::Bastion::MFA_TOTP_CONFIGURED_GROUP); $fnret or HEXIT($fnret); } -# remove the .otp file (non-fatal) -if (!unlink($home . '/' . OVH::Bastion::TOTP_FILENAME)) { - osh_warn("Couldn't remove the TOTP file ($!), this is not fatal, continuing anyway"); -} - osh_info "TOTP has been reset, " . ($account eq $self ? 'you' : $account) . " can re-enroll by using the `--osh selfMFASetupTOTP' command, if applicable"; HEXIT('OK'); diff --git a/bin/plugin/open/selfMFASetupTOTP b/bin/plugin/open/selfMFASetupTOTP index d98a72572..43aeda811 100755 --- a/bin/plugin/open/selfMFASetupTOTP +++ b/bin/plugin/open/selfMFASetupTOTP @@ -37,39 +37,53 @@ if ($ENV{'OSH_IN_INTERACTIVE_SESSION'}) { # do the TOTP enrollment -# first, check if the google-authenticator we have supports --issuer, if not, just omit it, it's not a deal-breaker -$fnret = OVH::Bastion::execute(cmd => ['google-authenticator', '-h'], must_succeed => 1); -$fnret or HEXIT($fnret); -my @additional_params; -if (grep { /--issuer/ } @{$fnret->value->{'stdout'}}) { - push @additional_params, "--issuer=" . OVH::Bastion::config('bastionName')->value; -} -if ($noConfirm && grep { /--no-confirm/ } @{$fnret->value->{'stdout'}}) { - push @additional_params, "--no-confirm"; -} +my $TOTPProvider = OVH::Bastion::config("TOTPProvider")->value; -@command = ( - 'script', '-q', '-c', "google-authenticator -f -t -Q UTF8 -r 3 -R 15 -w 2 -D " . join(" ", @additional_params) . " -l $self -s $HOME/" . OVH::Bastion::TOTP_FILENAME, - '/dev/null' -); -{ - local $ENV{'SHELL'} = '/bin/sh'; - $fnret = OVH::Bastion::execute(cmd => \@command, noisy_stderr => 1, noisy_stdout => 1, expects_stdin => 1, is_binary => 1, must_succeed => 1); +if ($TOTPProvider eq 'none') { + + # unconfigured: as we don't know which provider we have, just error out + osh_exit(R('ERR_CONFIGURATION_ERROR', msg => "No TOTP provider has been enabled, please check with your sysadmin if this is unexpected")); } -$fnret or osh_exit $fnret; +elsif ($TOTPProvider eq 'google-authenticator') { + + # first, check if the google-authenticator we have supports --issuer, if not, just omit it, it's not a deal-breaker + $fnret = OVH::Bastion::execute(cmd => ['google-authenticator', '-h'], must_succeed => 1); + $fnret or osh_exit($fnret); + my @additional_params; + if (grep { /--issuer/ } @{$fnret->value->{'stdout'}}) { + push @additional_params, "--issuer=" . OVH::Bastion::config('bastionName')->value; + } + if ($noConfirm && grep { /--no-confirm/ } @{$fnret->value->{'stdout'}}) { + push @additional_params, "--no-confirm"; + } -if (!$fnret) { - osh_exit('ERR_TOTP_SETUP_FAILED', msg => "Couldn't setup TOTP for your account, try again!"); + @command = ( + 'script', '-q', '-c', "google-authenticator -f -t -Q UTF8 -r 3 -R 15 -w 2 -D " . join(" ", @additional_params) . " -l $self -s $HOME/" . OVH::Bastion::TOTP_GAUTH_FILENAME, + '/dev/null' + ); + { + local $ENV{'SHELL'} = '/bin/sh'; + $fnret = OVH::Bastion::execute(cmd => \@command, noisy_stderr => 1, noisy_stdout => 1, expects_stdin => 1, is_binary => 1, must_succeed => 1); + } + + if (!$fnret) { + osh_exit('ERR_TOTP_SETUP_FAILED', msg => "Couldn't setup TOTP for your account, try again!"); + } + + chmod 0400, $HOME . '/' . OVH::Bastion::TOTP_GAUTH_FILENAME; } +elsif ($TOTPProvider eq 'duo') { -chmod 0400, $HOME . '/' . OVH::Bastion::TOTP_FILENAME; + # nothing to do locally, appart from marking the user as TOTP-active, which is done after this block. +} +else { + # unknown provider, this shouldn't happen + osh_exit(R('ERR_CONFIGURATION_ERROR', msg => "An unknown TOTP provider has been provided, please check with your sysadmin.")); +} # it worked, add the account to the proper system group @command = qw{ sudo -n -u root -- /usr/bin/env perl -T }; push @command, $OVH::Bastion::BASEPATH . '/bin/helper/osh-selfMFASetupTOTP'; push @command, '--account', $self; -$fnret = OVH::Bastion::helper(cmd => \@command); -$fnret or osh_exit $fnret; - -osh_exit $fnret; +osh_exit(OVH::Bastion::helper(cmd => \@command)); diff --git a/etc/bastion/bastion.conf.dist b/etc/bastion/bastion.conf.dist index 375875161..69280493b 100644 --- a/etc/bastion/bastion.conf.dist +++ b/etc/bastion/bastion.conf.dist @@ -410,6 +410,14 @@ # EXAMPLE: ["sudo","-n","-u","root","--","/sbin/pam_tally2","-u","%ACCOUNT%","-r"] or ["/usr/sbin/faillock","--reset"] "MFAPostCommand": [], # +# TOTPProvider (string) +# DESC: Defines which is the provider of the TOTP MFA, that will be used for the ``(self|account)MFA(Setup|Reset)TOTP`` commands. Allowed values are: +# - none: no TOTP provider is defined, the corresponding setup commands won't be available. +# - google-authenticator: the pam_google_authenticator.so module will be used, along with its corresponding setup binary. This is the default, for backward compatibility reasons. This is also what is configured in the provided pam templates. +# - duo: enable the use of the Duo PAM module (pam_duo.so), of course you need to set it up correctly in your `/etc/pam.d/sshd` file. +# DEFAULT: 'google-authenticator' +"TOTPProvider": "google-authenticator", +# ################# # > Other options # >> These options are either discouraged (in which case this is explained in the description) or rarely need to be modified. diff --git a/lib/perl/OVH/Bastion.pm b/lib/perl/OVH/Bastion.pm index 983075bc4..fbe0c3031 100644 --- a/lib/perl/OVH/Bastion.pm +++ b/lib/perl/OVH/Bastion.pm @@ -105,8 +105,8 @@ use constant { PAM_AUTH_BYPASS_GROUP => 'bastion-nopam', OSH_PUBKEY_AUTH_OPTIONAL_GROUP => 'osh-pubkey-auth-optional', - TOTP_FILENAME => '.otp', - TOTP_BASEDIR => '/var/otp', + TOTP_GAUTH_FILENAME => '.otp', + TOTP_BASEDIR => '/var/otp', # authorized_keys file, relative to the user's HOME directory. # if you change this, also change it in lib/shell/functions.inc diff --git a/lib/perl/OVH/Bastion/configuration.inc b/lib/perl/OVH/Bastion/configuration.inc index 75b10df75..bd446ade9 100644 --- a/lib/perl/OVH/Bastion/configuration.inc +++ b/lib/perl/OVH/Bastion/configuration.inc @@ -286,8 +286,9 @@ sub load_configuration { # 4/6) Strings that must be one item of a specific enum. foreach my $o ( - {name => 'defaultAccountEgressKeyAlgorithm', default => 'rsa', valid => [qw{ rsa ecdsa ed25519 }]}, - {name => 'accountMFAPolicy', default => 'enabled', valid => [qw{ disabled enabled password-required totp-required any-required }]}, + {name => 'defaultAccountEgressKeyAlgorithm', default => 'rsa', valid => [qw{ rsa ecdsa ed25519 }]}, + {name => 'accountMFAPolicy', default => 'enabled', valid => [qw{ disabled enabled password-required totp-required any-required }]}, + {name => 'TOTPProvider', default => 'google-authenticator', valid => [qw{ none google-authenticator duo }]}, ) { # if not defined, set to default value