mirror of
				https://github.com/ovh/the-bastion.git
				synced 2025-11-04 20:06:09 +08:00 
			
		
		
		
	feat: add support for Duo PAM auth as MFA (#249)
This commit is contained in:
		
							parent
							
								
									7dcbfeebc6
								
							
						
					
					
						commit
						89ecb2c0d7
					
				
					 6 changed files with 88 additions and 35 deletions
				
			
		| 
						 | 
				
			
			@ -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');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 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";
 | 
			
		||||
}
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
$fnret or osh_exit $fnret;
 | 
			
		||||
if ($TOTPProvider eq 'none') {
 | 
			
		||||
 | 
			
		||||
if (!$fnret) {
 | 
			
		||||
    osh_exit('ERR_TOTP_SETUP_FAILED', msg => "Couldn't setup TOTP for your account, try again!");
 | 
			
		||||
    # 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"));
 | 
			
		||||
}
 | 
			
		||||
elsif ($TOTPProvider eq 'google-authenticator') {
 | 
			
		||||
 | 
			
		||||
chmod 0400, $HOME . '/' . OVH::Bastion::TOTP_FILENAME;
 | 
			
		||||
    # 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";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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') {
 | 
			
		||||
 | 
			
		||||
    # 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));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -120,6 +120,7 @@ Policies applying to the bastion accounts themselves
 | 
			
		|||
- `MFAPasswordWarnDays`_
 | 
			
		||||
- `MFAPasswordInactiveDays`_
 | 
			
		||||
- `MFAPostCommand`_
 | 
			
		||||
- `TOTPProvider`_
 | 
			
		||||
 | 
			
		||||
Other options options
 | 
			
		||||
---------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -905,6 +906,20 @@ This can be used for example if you're using ``pam_tally2`` or ``pam_faillock``
 | 
			
		|||
The magic token ``%ACCOUNT%`` will be replaced by the account name.
 | 
			
		||||
Note that usually, ``pam_tally2`` can only be used by root (hence might require the proper sudoers configuration), while ``faillock`` can directly be used by unprivileged users to reset their counter.
 | 
			
		||||
 | 
			
		||||
.. _TOTPProvider:
 | 
			
		||||
 | 
			
		||||
TOTPProvider
 | 
			
		||||
************
 | 
			
		||||
 | 
			
		||||
:Type: ``string``
 | 
			
		||||
 | 
			
		||||
:Default: ``'google-authenticator'``
 | 
			
		||||
 | 
			
		||||
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 providers are 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.
 | 
			
		||||
 | 
			
		||||
Other options
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 providers are 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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -105,8 +105,7 @@ 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',
 | 
			
		||||
 | 
			
		||||
    # authorized_keys file, relative to the user's HOME directory.
 | 
			
		||||
    # if you change this, also change it in lib/shell/functions.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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue