all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH apiclient] support new tfa api
@ 2021-11-30  9:57 Wolfgang Bumiller
  2021-12-02 18:23 ` [pve-devel] applied: " Thomas Lamprecht
  0 siblings, 1 reply; 2+ messages in thread
From: Wolfgang Bumiller @ 2021-11-30  9:57 UTC (permalink / raw)
  To: pve-devel

Note that in PVE we should instantiate the API client with
`pve_new_format` in order to have this client also switch to
the new mechanism, otherwise the old api will be used which
does not support multiple factors or recovery keys.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 PVE/APIClient/LWP.pm | 85 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 80 insertions(+), 5 deletions(-)

diff --git a/PVE/APIClient/LWP.pm b/PVE/APIClient/LWP.pm
index 63f2177..27ecfed 100755
--- a/PVE/APIClient/LWP.pm
+++ b/PVE/APIClient/LWP.pm
@@ -93,7 +93,7 @@ sub update_ticket {
     $agent->default_header('Cookie', $cookie);
 }
 
-sub two_factor_auth_login {
+my sub two_factor_auth_login_old : prototype($$$) {
     my ($self, $type, $challenge) = @_;
 
     if ($type eq 'PVE:tfa') {
@@ -110,6 +110,74 @@ sub two_factor_auth_login {
     }
 }
 
+my sub extra_login_params : prototype($) {
+    my ($self) = @_;
+    return $self->{pve_new_format} ? ('new-format' => 1) : ();
+}
+
+my sub two_factor_auth_login : prototype($$$) {
+    my ($self, $challenge, $ticket) = @_;
+
+    raise("TFA-enabled login currently works only with a TTY.") if !-t STDIN;
+
+    $challenge = eval { from_json($challenge, { utf8 => 1 }) };
+    if (my $err = $@) {
+	raise("Bad TFA challenge: $err");
+    }
+    raise("Bad TFA challenge!") if !$challenge;
+
+    my @available;
+    push @available, 'totp' if $challenge->{totp};
+    push @available, 'recovery' if $challenge->{recovery};
+    push @available, 'yubico' if $challenge->{yubico};
+
+    my $selected;
+    if (@available == 1) {
+	$selected = $available[0];
+    } elsif (@available > 1) {
+	while (!defined($selected)) {
+	    print "Available TFA methods:\n";
+	    print "$_: $available[$_]\n" for (0..(@available - 1));
+	    print "Select TFA method: ";
+	    STDOUT->flush;
+	    my $response = <STDIN>;
+	    if ($response =~ /^\s*(\d+)\s*$/) {
+		$selected = int($response);
+	    }
+	}
+	$selected = $available[$selected];
+    } else {
+	raise("TFA required, but available authentication types are not supported, aborting!");
+    }
+
+    if ($selected eq 'recovery') {
+	my $keys = $challenge->{recovery};
+	if (@$keys <= 3) {
+	    print("WARNING: Few recovery keys remaining: ");
+	} else {
+	    print("The following recovery codes are available: ");
+	}
+	print(join(', ', @$keys), "\n");
+    }
+
+    print "Enter $selected code for user $self->{username}: ";
+    STDOUT->flush;
+    my $tfa_response = <STDIN>;
+    chomp $tfa_response;
+
+    return $self->post(
+	'/api2/json/access/ticket',
+	{
+	    username => $self->{username},
+	    password => "$selected:$tfa_response",
+	    'tfa-challenge' => $ticket,
+	    (extra_login_params($self))
+	},
+    );
+}
+
+my $new_tfa_ticket_re = qr/^[^\s:]+:!tfa!([^:]+):/;
+my $old_tfa_ticket_re = qr/^([^\s!]+)![^!]*(!([0-9a-zA-Z\/.=_\-+]+))?$/;
 sub login {
     my ($self) = @_;
 
@@ -127,7 +195,8 @@ sub login {
     my $exec_login = sub {
 	return $ua->post($uri, {
 	    username => $username,
-	    password => $self->{password} || ''
+	    password => $self->{password} || '',
+	    (extra_login_params($self))
 	});
     };
 
@@ -152,10 +221,15 @@ sub login {
     $self->update_csrftoken($data->{CSRFPreventionToken});
 
     # handle two-factor login
-    my $tfa_ticket_re = qr/^([^\s!]+)![^!]*(!([0-9a-zA-Z\/.=_\-+]+))?$/;
-    if ($data->{ticket} =~ m/$tfa_ticket_re/) {
+    my $ticket = $data->{ticket};
+    if ($ticket =~ $new_tfa_ticket_re) {
+	my $challenge = uri_unescape($1);
+	$data = two_factor_auth_login($self, $challenge, $ticket);
+	$self->update_ticket($data->{ticket});
+    } elsif ($ticket =~ $old_tfa_ticket_re) {
+	# handle old-style two-factor login for PVE:
 	my ($type, $challenge) = ($1, $2);
-	$data = $self->two_factor_auth_login($type, $challenge);
+	$data = two_factor_auth_login_old($self, $type, $challenge);
 	$self->update_ticket($data->{ticket});
     }
 
@@ -329,6 +403,7 @@ sub new {
 	},
 	register_fingerprint_cb => $param{register_fingerprint_cb},
 	timeout => $param{timeout} || 60,
+	pve_new_format => $param{pve_new_format},
     };
     bless $self, $class;
 
-- 
2.30.2





^ permalink raw reply	[flat|nested] 2+ messages in thread

* [pve-devel] applied:  [PATCH apiclient] support new tfa api
  2021-11-30  9:57 [pve-devel] [PATCH apiclient] support new tfa api Wolfgang Bumiller
@ 2021-12-02 18:23 ` Thomas Lamprecht
  0 siblings, 0 replies; 2+ messages in thread
From: Thomas Lamprecht @ 2021-12-02 18:23 UTC (permalink / raw)
  To: Proxmox VE development discussion, Wolfgang Bumiller

On 30.11.21 10:57, Wolfgang Bumiller wrote:
> Note that in PVE we should instantiate the API client with
> `pve_new_format` in order to have this client also switch to
> the new mechanism, otherwise the old api will be used which
> does not support multiple factors or recovery keys.
> 
> Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
> ---
>  PVE/APIClient/LWP.pm | 85 +++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 80 insertions(+), 5 deletions(-)
> 
>

applied, thanks!




^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2021-12-02 18:24 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-30  9:57 [pve-devel] [PATCH apiclient] support new tfa api Wolfgang Bumiller
2021-12-02 18:23 ` [pve-devel] applied: " Thomas Lamprecht

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal