From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH apiclient] support new tfa api
Date: Tue, 30 Nov 2021 10:57:40 +0100 [thread overview]
Message-ID: <20211130095740.35774-1-w.bumiller@proxmox.com> (raw)
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
next reply other threads:[~2021-11-30 9:57 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-11-30 9:57 Wolfgang Bumiller [this message]
2021-12-02 18:23 ` [pve-devel] applied: " Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20211130095740.35774-1-w.bumiller@proxmox.com \
--to=w.bumiller@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.