diff --git a/bin/admin/install b/bin/admin/install index b6a5ef1..eca7925 100755 --- a/bin/admin/install +++ b/bin/admin/install @@ -872,7 +872,7 @@ if [ "$nothing" = 0 ]; then at_least_one_error=0 for group in bastion-users \ mfa-password-reqd mfa-password-bypass mfa-password-configd \ - mfa-totp-reqd mfa-totp-bypass mfa-totp-configd bastion-nopam mfa-any + mfa-totp-reqd mfa-totp-bypass mfa-totp-configd bastion-nopam osh-pubkey-auth-optional do if getent group "$group" >/dev/null 2>&1; then : diff --git a/bin/helper/osh-accountModify b/bin/helper/osh-accountModify index 4780f63..dc8e533 100755 --- a/bin/helper/osh-accountModify +++ b/bin/helper/osh-accountModify @@ -280,45 +280,45 @@ foreach my $tuple (@modify) { } } } - elsif ($key eq 'mfa-any') { - $fnret = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_ANY_GROUP); + elsif ($key eq 'pubkey-auth-optional') { + $fnret = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::OSH_PUBKEY_AUTH_OPTIONAL_GROUP); if ($value eq 'yes') { { - osh_info "Setting authentication requirements to pubkey OR pam (if a password/TOTP is set) for this account..."; + osh_info "Making public key authentication optional for this account..."; if ($fnret) { osh_info "... no change was required"; $result{$jsonkey} = R('OK_NO_CHANGE'); last; } - $fnret = OVH::Bastion::sys_addmembertogroup(user => $account, group => OVH::Bastion::MFA_ANY_GROUP, noisy_stderr => 1); + $fnret = OVH::Bastion::sys_addmembertogroup(user => $account, group => OVH::Bastion::OSH_PUBKEY_AUTH_OPTIONAL_GROUP, noisy_stderr => 1); if (!$fnret) { - osh_warn "... error while setting the alternative authentication option"; + osh_warn "... error while setting the optional pubkey option"; $result{$jsonkey} = R('ERR_ADDING_TO_GROUP'); last; } - osh_info "... done, this account can now authenticate with either pubkey or pam (if a password/TOTP is set)"; + osh_info "... done, this account can now authenticate with or without a pubkey if a password/TOTP is set"; $result{$jsonkey} = R('OK'); } } elsif ($value eq 'no') { { - osh_info "Setting authentication to pubkey AND pam (if a password/TOTP is set) for this account..."; + osh_info "Making pubkey authentication mandatory for this account..."; if (!$fnret) { osh_info "... no change was required"; $result{$jsonkey} = R('OK_NO_CHANGE'); last; } - $fnret = OVH::Bastion::sys_delmemberfromgroup(user => $account, group => OVH::Bastion::MFA_ANY_GROUP, noisy_stderr => 1); + $fnret = OVH::Bastion::sys_delmemberfromgroup(user => $account, group => OVH::Bastion::OSH_PUBKEY_AUTH_OPTIONAL_GROUP, noisy_stderr => 1); if (!$fnret) { - osh_warn "... error while removing the alternative authentication option"; + osh_warn "... error while removing the optional pubkey option"; $result{$jsonkey} = R('ERR_REMOVING_FROM_GROUP'); last; } - osh_info "... done, this account now requires to authenticate with pubkey AND pam (if a password/TOTP is set)"; + osh_info "... done, this account now requires a pubkey to authenticate"; $result{$jsonkey} = R('OK'); } } diff --git a/bin/plugin/restricted/accountInfo b/bin/plugin/restricted/accountInfo index 37636cb..cbf26e9 100755 --- a/bin/plugin/restricted/accountInfo +++ b/bin/plugin/restricted/accountInfo @@ -267,8 +267,8 @@ if (OVH::Bastion::is_auditor(account => $self)) { $ret{'pam_auth_bypass'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::PAM_AUTH_BYPASS_GROUP) ? 1 : 0; osh_info "- PAM authentication bypass is " . ($ret{'pam_auth_bypass'} ? colored('enabled', 'green') : colored('disabled', 'blue')); - $ret{'mfa_any'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_ANY_GROUP) ? 1 : 0; - osh_info "- Alternative authentication logic (allow both pubkey alone and PAM alone) is " . ($ret{'mfa_any'} ? colored('enabled', 'green') : colored('disabled', 'blue')); + $ret{'pubkey_auth_optional'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::OSH_PUBKEY_AUTH_OPTIONAL_GROUP) ? 1 : 0; + osh_info "- Optional public key authentication is " . ($ret{'pubkey_auth_optional'} ? colored('enabled', 'green') : colored('disabled', 'blue')); $ret{'personal_egress_mfa_required'} = OVH::Bastion::account_config(account => $account, key => "personal_egress_mfa_required")->value; $ret{'personal_egress_mfa_required'} ||= 'none'; # no config means no mfa diff --git a/bin/plugin/restricted/accountList b/bin/plugin/restricted/accountList index 3dfe94f..c8b93c0 100755 --- a/bin/plugin/restricted/accountList +++ b/bin/plugin/restricted/accountList @@ -131,14 +131,14 @@ foreach my $account (sort keys %$accounts) { $states{'can_connect'} = ($states{'is_active'} && !$states{'is_expired'}) ? 1 : 0; - $states{'mfa_password_required'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_REQUIRED_GROUP) ? 1 : 0; - $states{'mfa_password_configured'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_CONFIGURED_GROUP) ? 1 : 0; - $states{'mfa_password_bypass'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_BYPASS_GROUP) ? 1 : 0; - $states{'mfa_totp_required'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_REQUIRED_GROUP) ? 1 : 0; - $states{'mfa_totp_configured'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_CONFIGURED_GROUP) ? 1 : 0; - $states{'mfa_totp_bypass'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_BYPASS_GROUP) ? 1 : 0; - $states{'pam_auth_bypass'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::PAM_AUTH_BYPASS_GROUP) ? 1 : 0; - $states{'mfa_any'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_ANY_GROUP) ? 1 : 0; + $states{'mfa_password_required'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_REQUIRED_GROUP) ? 1 : 0; + $states{'mfa_password_configured'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_CONFIGURED_GROUP) ? 1 : 0; + $states{'mfa_password_bypass'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_PASSWORD_BYPASS_GROUP) ? 1 : 0; + $states{'mfa_totp_required'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_REQUIRED_GROUP) ? 1 : 0; + $states{'mfa_totp_configured'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_CONFIGURED_GROUP) ? 1 : 0; + $states{'mfa_totp_bypass'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::MFA_TOTP_BYPASS_GROUP) ? 1 : 0; + $states{'pam_auth_bypass'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::PAM_AUTH_BYPASS_GROUP) ? 1 : 0; + $states{'pubkey_auth_optional'} = OVH::Bastion::is_user_in_group(user => $account, group => OVH::Bastion::OSH_PUBKEY_AUTH_OPTIONAL_GROUP) ? 1 : 0; if ($fnretPassword) { $states{"password_$_"} = $fnretPassword->value->{$account}{$_} for (keys %{$fnretPassword->value->{$account}}); @@ -160,7 +160,7 @@ foreach my $account (sort keys %$accounts) { push @mfaTOTP, 'bypass' if $states{'mfa_totp_bypass'}; osh_info sprintf( -"%-18s %6d active:%-12s expired:%-12s can_connect:%-12s already_seen:%-12s mfa_password:%-25s mfa_totp:%-25s pam_bypass:%-12s mfa_any:%-12s pass_status:%-15s pass_changed:%-10s pass_min_days:%-3d pass_max_days:%-3d pass_warn_days:%-3d %s\n", +"%-18s %6d active:%-12s expired:%-12s can_connect:%-12s already_seen:%-12s mfa_password:%-25s mfa_totp:%-25s pam_bypass:%-12s pubkey_auth_optional:%-12s pass_status:%-15s pass_changed:%-10s pass_min_days:%-3d pass_max_days:%-3d pass_warn_days:%-3d %s\n", $account, $accounts->{$account}{'uid'}, tristate2str($states{'is_active'}), @@ -169,8 +169,8 @@ foreach my $account (sort keys %$accounts) { tristate2str($states{'already_seen_before'}), @mfaPassword ? colored(join(',', @mfaPassword), 'green') : colored('-', 'blue'), @mfaTOTP ? colored(join(',', @mfaTOTP), 'green') : colored('-', 'blue'), - tristate2str($states{'pam_auth_bypass'}, 1), - tristate2str($states{'mfa_any'}, 1), + tristate2str($states{'pam_auth_bypass'}, 1), + tristate2str($states{'pubkey_auth_optional'}, 1), ( $states{'password_password'} eq 'locked' ? colored('locked', 'blue') diff --git a/bin/plugin/restricted/accountModify b/bin/plugin/restricted/accountModify index 5cf1d60..623b3a3 100755 --- a/bin/plugin/restricted/accountModify +++ b/bin/plugin/restricted/accountModify @@ -23,7 +23,7 @@ my $remainingOptions = OVH::Bastion::Plugin::begin( "idle-ignore=s" => \$modify{'idle-ignore'}, "max-inactive-days=i" => \$modify{'max-inactive-days'}, "osh-only=s" => \$modify{'osh-only'}, - "mfa-any=s" => \$modify{'mfa-any'}, + "pubkey-auth-optional=s" => \$modify{'pubkey-auth-optional'}, }, helptext => <<'EOF', Modify an account configuration @@ -55,10 +55,12 @@ Usage: --osh SCRIPT_NAME --account ACCOUNT [--option value [--option value [...] 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 - --mfa-any yes|no Control the ingress login requirements for pubkey and pam (when a password and/or TOTP is set). - When disabled, the user needs pubkey AND pam, this is the default. - When enabled, the user can authenticate with either pubkey OR pam. - If the account has no password/TOTP, this option has no effect, i.e: pubkey is used. Egress is not affected. + --pubkey-auth-optional yes|no Make the public key optional on ingress for the account (default is 'no'). + When enabled the public key part of the authentication becomes optional when a password and/or TOTP is defined, + allowing to login with just the password/TOTP. If no password/TOTP is defined then the public key is the only way to authenticate, + because some form of authentication is always required. + When disabled, the public key is always required. + Egress is not affected. EOF ); @@ -86,7 +88,7 @@ foreach my $key (qw{ mfa-password-required mfa-totp-required }) { osh_exit 'ERR_INVALID_PARAMETER', "Expected '--$key yes' or '--$key no' or '--$key bypass' instead of '--$key $modify{$key}'"; } } -foreach my $key (qw{ always-active pam-auth-bypass idle-ignore osh-only mfa-any }) { +foreach my $key (qw{ always-active pam-auth-bypass idle-ignore osh-only pubkey-auth-optional }) { next unless $modify{$key}; if (not grep { $modify{$key} eq $_ } qw{ yes no }) { help(); diff --git a/bin/plugin/restricted/accountModify.json b/bin/plugin/restricted/accountModify.json index 2dcc7ca..81d8933 100644 --- a/bin/plugin/restricted/accountModify.json +++ b/bin/plugin/restricted/accountModify.json @@ -1,13 +1,13 @@ { "master_only": true, "interactive": [ - "accountModify" , {"ac": ["--account"]}, - "accountModify --account" , {"ac": [""]}, - "accountModify --account \\S+" , {"ac": ["--mfa-password-required","--mfa-totp-required","--pam-auth-bypass","--always-active","--egress-strict-host-key-checking","--personal-egress-mfa-required","--idle-ignore","--mfa-any"]}, - "accountModify --account \\S+ .*(--mfa-password-required|--mfa-totp-required)" , {"ac": ["yes","no","bypass"]}, - "accountModify --account \\S+ .*(--pam-auth-bypass|--mfa-auth-bypass|--always-active|idle-ignore|--mfa-any)", {"ac": ["yes","no"]}, - "accountModify --account \\S+ .*(--egress-strict-host-key-checking)" , {"ac": ["yes","accept-new","no","ask","default","bypass"]}, - "accountModify --account \\S+ .*(--personal-egress-mfa-required)" , {"ac": ["password","totp","any","none"]}, - "accountModify --account \\S+ .*(yes|accept-new|no|bypass|ask|default|totp|password|none)" , {"ac": ["--mfa-password-required","--mfa-totp-required","--pam-auth-bypass","--always-active","--egress-strict-host-key-checking","--personal-egress-mfa-required","--idle-ignore","-mfa-any",""]} + "accountModify" , {"ac": ["--account"]}, + "accountModify --account" , {"ac": [""]}, + "accountModify --account \\S+" , {"ac": ["--mfa-password-required","--mfa-totp-required","--pam-auth-bypass","--always-active","--egress-strict-host-key-checking","--personal-egress-mfa-required","--idle-ignore","--pubkey-auth-optional"]}, + "accountModify --account \\S+ .*(--mfa-password-required|--mfa-totp-required)" , {"ac": ["yes","no","bypass"]}, + "accountModify --account \\S+ .*(--pam-auth-bypass|--mfa-auth-bypass|--always-active|idle-ignore|--pubkey-auth-optional)", {"ac": ["yes","no"]}, + "accountModify --account \\S+ .*(--egress-strict-host-key-checking)" , {"ac": ["yes","accept-new","no","ask","default","bypass"]}, + "accountModify --account \\S+ .*(--personal-egress-mfa-required)" , {"ac": ["password","totp","any","none"]}, + "accountModify --account \\S+ .*(yes|accept-new|no|bypass|ask|default|totp|password|none)" , {"ac": ["--mfa-password-required","--mfa-totp-required","--pam-auth-bypass","--always-active","--egress-strict-host-key-checking","--personal-egress-mfa-required","--idle-ignore","--pubkey-auth-optional",""]} ] } diff --git a/doc/sphinx-plugins-override/accountInfo.rst b/doc/sphinx-plugins-override/accountInfo.rst index 4aed987..273d427 100644 --- a/doc/sphinx-plugins-override/accountInfo.rst +++ b/doc/sphinx-plugins-override/accountInfo.rst @@ -36,7 +36,7 @@ Output example ~ - Additional TOTP authentication bypass is disabled for this account ~ - Additional TOTP authentication is disabled ~ - PAM authentication bypass is disabled - ~ - Alternative authentication logic (allow both pubkey alone and PAM alone) is disabled + ~ - Optional public key authentication is disabled ~ - MFA policy on personal accesses (using personal keys) on egress side is: password ~ Account PAM UNIX password information (used for password MFA): diff --git a/doc/sphinx/plugins/restricted/accountInfo.rst b/doc/sphinx/plugins/restricted/accountInfo.rst index ea078ee..412a375 100644 --- a/doc/sphinx/plugins/restricted/accountInfo.rst +++ b/doc/sphinx/plugins/restricted/accountInfo.rst @@ -60,7 +60,7 @@ Output example ~ - Additional TOTP authentication bypass is disabled for this account ~ - Additional TOTP authentication is disabled ~ - PAM authentication bypass is disabled - ~ - Alternative authentication logic (allow both pubkey alone and PAM alone) is disabled + ~ - Optional public key authentication is disabled ~ - MFA policy on personal accesses (using personal keys) on egress side is: password ~ Account PAM UNIX password information (used for password MFA): diff --git a/doc/sphinx/plugins/restricted/accountModify.rst b/doc/sphinx/plugins/restricted/accountModify.rst index dd56638..df63030 100644 --- a/doc/sphinx/plugins/restricted/accountModify.rst +++ b/doc/sphinx/plugins/restricted/accountModify.rst @@ -69,10 +69,12 @@ Modify an account configuration If enabled, this account can only use ``--osh`` commands, and can't connect anywhere through the bastion -.. option:: --mfa-any yes|no +.. option:: --pubkey-auth-optional yes|no - Control the ingress login requirements for pubkey and pam (when a password and/or TOTP is set). + Make the public key optional on ingress for the account (default is 'no'). - When disabled, the user needs pubkey AND pam, this is the default. - When enabled, the user can authenticate with either pubkey OR pam. - If the account has no password/TOTP, this option has no effect, i.e: pubkey is used. Egress is not affected. + When enabled the public key part of the authentication becomes optional when a password and/or TOTP is defined, + allowing to login with just the password/TOTP. If no password/TOTP is defined then the public key is the only way to authenticate, + because some form of authentication is always required. + When disabled, the public key is always required. + Egress is not affected. diff --git a/etc/ssh/sshd_config.centos7 b/etc/ssh/sshd_config.centos7 index e78a612..da435bc 100644 --- a/etc/ssh/sshd_config.centos7 +++ b/etc/ssh/sshd_config.centos7 @@ -129,9 +129,9 @@ UsePAM yes # Unconditionally skip PAM auth for members of the bastion-nopam group Match Group bastion-nopam AuthenticationMethods publickey -# if in one of the mfa groups AND the mfa-any group, use publickey OR pam -Match Group mfa-totp-configd,mfa-password-configd Group mfa-any - AuthenticationMethods publickey keyboard-interactive:pam +# if in one of the mfa groups AND the osh-pubkey-auth-optional group, use publickey+pam OR pam +Match Group mfa-totp-configd,mfa-password-configd Group osh-pubkey-auth-optional + AuthenticationMethods publickey,keyboard-interactive:pam keyboard-interactive:pam # if in one of the mfa groups, use publickey AND pam Match Group mfa-totp-configd,mfa-password-configd AuthenticationMethods publickey,keyboard-interactive:pam diff --git a/etc/ssh/sshd_config.centos8 b/etc/ssh/sshd_config.centos8 index e78a612..da435bc 100644 --- a/etc/ssh/sshd_config.centos8 +++ b/etc/ssh/sshd_config.centos8 @@ -129,9 +129,9 @@ UsePAM yes # Unconditionally skip PAM auth for members of the bastion-nopam group Match Group bastion-nopam AuthenticationMethods publickey -# if in one of the mfa groups AND the mfa-any group, use publickey OR pam -Match Group mfa-totp-configd,mfa-password-configd Group mfa-any - AuthenticationMethods publickey keyboard-interactive:pam +# if in one of the mfa groups AND the osh-pubkey-auth-optional group, use publickey+pam OR pam +Match Group mfa-totp-configd,mfa-password-configd Group osh-pubkey-auth-optional + AuthenticationMethods publickey,keyboard-interactive:pam keyboard-interactive:pam # if in one of the mfa groups, use publickey AND pam Match Group mfa-totp-configd,mfa-password-configd AuthenticationMethods publickey,keyboard-interactive:pam diff --git a/etc/ssh/sshd_config.debian10 b/etc/ssh/sshd_config.debian10 index 6afb10d..421d17e 100644 --- a/etc/ssh/sshd_config.debian10 +++ b/etc/ssh/sshd_config.debian10 @@ -133,9 +133,9 @@ UsePAM yes # Unconditionally skip PAM auth for members of the bastion-nopam group Match Group bastion-nopam AuthenticationMethods publickey -# if in one of the mfa groups AND the mfa-any group, use publickey OR pam -Match Group mfa-totp-configd,mfa-password-configd Group mfa-any - AuthenticationMethods publickey keyboard-interactive:pam +# if in one of the mfa groups AND the osh-pubkey-auth-optional group, use publickey+pam OR pam +Match Group mfa-totp-configd,mfa-password-configd Group osh-pubkey-auth-optional + AuthenticationMethods publickey,keyboard-interactive:pam keyboard-interactive:pam # if in one of the mfa groups, use publickey AND pam Match Group mfa-totp-configd,mfa-password-configd AuthenticationMethods publickey,keyboard-interactive:pam diff --git a/etc/ssh/sshd_config.debian11 b/etc/ssh/sshd_config.debian11 index 7445844..aa80742 100644 --- a/etc/ssh/sshd_config.debian11 +++ b/etc/ssh/sshd_config.debian11 @@ -133,9 +133,9 @@ UsePAM yes # Unconditionally skip PAM auth for members of the bastion-nopam group Match Group bastion-nopam AuthenticationMethods publickey -# if in one of the mfa groups AND the mfa-any group, use publickey OR pam -Match Group mfa-totp-configd,mfa-password-configd Group mfa-any - AuthenticationMethods publickey keyboard-interactive:pam +# if in one of the mfa groups AND the osh-pubkey-auth-optional group, use publickey+pam OR pam +Match Group mfa-totp-configd,mfa-password-configd Group osh-pubkey-auth-optional + AuthenticationMethods publickey,keyboard-interactive:pam keyboard-interactive:pam # if in one of the mfa groups, use publickey AND pam Match Group mfa-totp-configd,mfa-password-configd AuthenticationMethods publickey,keyboard-interactive:pam diff --git a/etc/ssh/sshd_config.debian8 b/etc/ssh/sshd_config.debian8 index 1651c2d..1733c31 100644 --- a/etc/ssh/sshd_config.debian8 +++ b/etc/ssh/sshd_config.debian8 @@ -136,9 +136,9 @@ UsePAM yes # Unconditionally skip PAM auth for members of the bastion-nopam group Match Group bastion-nopam AuthenticationMethods publickey -# if in one of the mfa groups AND the mfa-any group, use publickey OR pam -Match Group mfa-totp-configd,mfa-password-configd Group mfa-any - AuthenticationMethods publickey keyboard-interactive:pam +# if in one of the mfa groups AND the osh-pubkey-auth-optional group, use publickey+pam OR pam +Match Group mfa-totp-configd,mfa-password-configd Group osh-pubkey-auth-optional + AuthenticationMethods publickey,keyboard-interactive:pam keyboard-interactive:pam # if in one of the mfa groups, use publickey AND pam Match Group mfa-totp-configd,mfa-password-configd AuthenticationMethods publickey,keyboard-interactive:pam diff --git a/etc/ssh/sshd_config.debian9 b/etc/ssh/sshd_config.debian9 index 1651c2d..1733c31 100644 --- a/etc/ssh/sshd_config.debian9 +++ b/etc/ssh/sshd_config.debian9 @@ -136,9 +136,9 @@ UsePAM yes # Unconditionally skip PAM auth for members of the bastion-nopam group Match Group bastion-nopam AuthenticationMethods publickey -# if in one of the mfa groups AND the mfa-any group, use publickey OR pam -Match Group mfa-totp-configd,mfa-password-configd Group mfa-any - AuthenticationMethods publickey keyboard-interactive:pam +# if in one of the mfa groups AND the osh-pubkey-auth-optional group, use publickey+pam OR pam +Match Group mfa-totp-configd,mfa-password-configd Group osh-pubkey-auth-optional + AuthenticationMethods publickey,keyboard-interactive:pam keyboard-interactive:pam # if in one of the mfa groups, use publickey AND pam Match Group mfa-totp-configd,mfa-password-configd AuthenticationMethods publickey,keyboard-interactive:pam diff --git a/etc/ssh/sshd_config.freebsd b/etc/ssh/sshd_config.freebsd index fb84255..b1860cb 100644 --- a/etc/ssh/sshd_config.freebsd +++ b/etc/ssh/sshd_config.freebsd @@ -126,9 +126,9 @@ UsePAM yes # Unconditionally skip PAM auth for members of the bastion-nopam group Match Group bastion-nopam AuthenticationMethods publickey -# if in one of the mfa groups AND the mfa-any group, use publickey OR pam -Match Group mfa-totp-configd,mfa-password-configd Group mfa-any - AuthenticationMethods publickey keyboard-interactive:pam +# if in one of the mfa groups AND the osh-pubkey-auth-optional group, use publickey+pam OR pam +Match Group mfa-totp-configd,mfa-password-configd Group osh-pubkey-auth-optional + AuthenticationMethods publickey,keyboard-interactive:pam keyboard-interactive:pam # if in one of the mfa groups, use publickey AND pam Match Group mfa-totp-configd,mfa-password-configd AuthenticationMethods publickey,keyboard-interactive:pam diff --git a/lib/perl/OVH/Bastion.pm b/lib/perl/OVH/Bastion.pm index 248352c..983075b 100644 --- a/lib/perl/OVH/Bastion.pm +++ b/lib/perl/OVH/Bastion.pm @@ -96,14 +96,14 @@ use constant { }; use constant { - MFA_PASSWORD_REQUIRED_GROUP => 'mfa-password-reqd', - MFA_PASSWORD_CONFIGURED_GROUP => 'mfa-password-configd', - MFA_PASSWORD_BYPASS_GROUP => 'mfa-password-bypass', - MFA_TOTP_REQUIRED_GROUP => 'mfa-totp-reqd', - MFA_TOTP_CONFIGURED_GROUP => 'mfa-totp-configd', - MFA_TOTP_BYPASS_GROUP => 'mfa-totp-bypass', - PAM_AUTH_BYPASS_GROUP => 'bastion-nopam', - MFA_ANY_GROUP => 'mfa-any', + MFA_PASSWORD_REQUIRED_GROUP => 'mfa-password-reqd', + MFA_PASSWORD_CONFIGURED_GROUP => 'mfa-password-configd', + MFA_PASSWORD_BYPASS_GROUP => 'mfa-password-bypass', + MFA_TOTP_REQUIRED_GROUP => 'mfa-totp-reqd', + MFA_TOTP_CONFIGURED_GROUP => 'mfa-totp-configd', + MFA_TOTP_BYPASS_GROUP => 'mfa-totp-bypass', + PAM_AUTH_BYPASS_GROUP => 'bastion-nopam', + OSH_PUBKEY_AUTH_OPTIONAL_GROUP => 'osh-pubkey-auth-optional', TOTP_FILENAME => '.otp', TOTP_BASEDIR => '/var/otp', diff --git a/tests/functional/tests.d/370-mfa.sh b/tests/functional/tests.d/370-mfa.sh index 39e0da7..27c98e9 100644 --- a/tests/functional/tests.d/370-mfa.sh +++ b/tests/functional/tests.d/370-mfa.sh @@ -381,16 +381,18 @@ testsuite_mfa() success a0_remove_mfa_req_a4_dupe $a0 --osh accountModify --account $account4 --pam-auth-bypass no --mfa-totp-required no --mfa-password-required no json .error_code OK .command accountModify .value.pam_auth_bypass.error_code OK_NO_CHANGE .value.mfa_totp_required.error_code OK_NO_CHANGE .value.mfa_password_required.error_code OK_NO_CHANGE - # remove totp from account4 to simplify the following mfa-any tests + # pubkey-auth-optional + + # remove totp from account4 to simplify the following tests grant accountMFAResetTOTP - success mfa a0_nototp_a4 $a0 --osh accountMFAResetTOTP --account $account4 + success a0_nototp_a4 $a0 --osh accountMFAResetTOTP --account $account4 json .command accountMFAResetTOTP .error_code OK revoke accountMFAResetTOTP - # no mfa-any, success with pubkey and password - script mfa a4_no_mfaany_login_pubkey_pam "echo 'set timeout 30; \ + # pubkey-auth-optional disabled: success with pubkey and password + script a4_no_pubkeyauthoptional_login_pubkey_pam "echo 'set timeout 30; \ spawn $a4 --osh groupList; \ expect \"word:\" { sleep 0.2; send \"$a4_password\\n\"; }; \ expect eof; \ @@ -401,8 +403,8 @@ testsuite_mfa() contain REGEX 'Password:|Password for' json .command groupList .error_code OK_EMPTY - # no mfa-any, fail with pubkey but no password (timeout) - script mfa a4_no_mfaany_login_pubkey_nopam $a4 --osh groupList + # pubkey-auth-optional disabled: fail with pubkey but no password (timeout) + script a4_no_pubkeyauthoptional_login_pubkey_nopam $a4 --osh groupList retvalshouldbe 124 contain 'Multi-Factor Authentication enabled, an additional authentication factor is required (password).' contain 'Your password expires on' @@ -410,27 +412,35 @@ testsuite_mfa() contain REGEX 'Password:|Password for' nocontain 'JSON_OUTPUT' - # no mfa-any, fail with no pubkey (never gets to ask for the password) - script mfa a4_no_mfaany_login_nopubkey_pam $a4np --osh groupList + # pubkey-auth-optional disabled: fail with no pubkey (never gets to ask for the password) + script a4_no_pubkeyauthoptional_login_nopubkey_pam $a4np --osh groupList retvalshouldbe 255 contain 'Permission denied (publickey).' nocontain 'password' nocontain 'JSON_OUTPUT' - # set mfa-any on account4 - success mfa a0_set_mfaany_a4 $a0 --osh accountModify --account $account4 --mfa-any yes - json .error_code OK .command accountModify .value.mfa_any.error_code OK + # set pubkey-auth-optional on account4 + success a0_set_pubkeyauthoptional_a4 $a0 --osh accountModify --account $account4 --pubkey-auth-optional yes + json .error_code OK .command accountModify .value.pubkey_auth_optional.error_code OK - # set mfa-any on account4 (dupe) - success mfa a0_set_mfaany_a4_dupe $a0 --osh accountModify --account $account4 --mfa-any yes - json .error_code OK .command accountModify .value.mfa_any.error_code OK_NO_CHANGE + # set pubkey-auth-optional on account4 (dupe) + success a0_set_pubkeyauthoptional_a4_dupe $a0 --osh accountModify --account $account4 --pubkey-auth-optional yes + json .error_code OK .command accountModify .value.pubkey_auth_optional.error_code OK_NO_CHANGE - # success with pubkey but no password - success mfa a4_mfaany_login_pubkey_nopam $a4 --osh groupList + # pubkey-auth-optional enabled: success with pubkey and password + script a4_pubkeyauthoptional_login_pubkey_pam "echo 'set timeout 30; \ + spawn $a4 --osh groupList; \ + expect \"word:\" { sleep 0.2; send \"$a4_password\\n\"; }; \ + expect eof; \ + lassign [wait] pid spawnid value value; \ + exit \$value' | expect -f -" + retvalshouldbe 0 + contain 'Multi-Factor Authentication enabled, an additional authentication factor is required (password).' + contain REGEX 'Password:|Password for' json .command groupList .error_code OK_EMPTY - # success with password but no pubkey - script mfa a4_mfaany_login_nopubkey_pam "echo 'set timeout 30; \ + # pubkey-auth-optional enabled: success with password only + script a4_pubkeyauthoptional_login_nopubkey_pam "echo 'set timeout 30; \ spawn $a4np --osh groupList; \ expect \"word:\" { sleep 0.2; send \"$a4_password\\n\"; }; \ expect eof; \ @@ -441,13 +451,22 @@ testsuite_mfa() contain REGEX 'Password:|Password for' json .command groupList .error_code OK_EMPTY - # unset mfa-any on account4 - success mfa a0_unset_mfaany_a4 $a0 --osh accountModify --account $account4 --mfa-any no - json .error_code OK .command accountModify .value.mfa_any.error_code OK + # pubkey-auth-optional enabled: fail with pubkey only + script a4_pubkeyauthoptional_login_pubkey_nopam $a4 --osh groupList + retvalshouldbe 124 + contain 'Multi-Factor Authentication enabled, an additional authentication factor is required (password).' + contain 'Your password expires on' + contain 'in 89 days' + contain REGEX 'Password:|Password for' + nocontain 'JSON_OUTPUT' + + # unset pubkey-auth-optional on account4 + success a0_unset_pubkeyauthoptional_a4 $a0 --osh accountModify --account $account4 --pubkey-auth-optional no + json .error_code OK .command accountModify .value.pubkey_auth_optional.error_code OK # unset mfa-any on account4 (dupe) - success mfa a0_unset_mfaany_a4_dupe $a0 --osh accountModify --account $account4 --mfa-any no - json .error_code OK .command accountModify .value.mfa_any.error_code OK_NO_CHANGE + success a0_unset_pubkeyauthoptional_a4_dupe $a0 --osh accountModify --account $account4 --pubkey-auth-optional no + json .error_code OK .command accountModify .value.pubkey_auth_optional.error_code OK_NO_CHANGE