@@ -18,6 +18,7 @@ use futures::{FutureExt as _, StreamExt as _};
1818use http:: header:: WWW_AUTHENTICATE ;
1919use http:: status:: StatusCode ;
2020use memmap2:: Mmap ;
21+ use progenitor_client:: ResponseValue ;
2122use reqwest:: Upgraded ;
2223use rustix:: termios:: tcgetwinsize;
2324use thiserror:: Error ;
@@ -28,7 +29,7 @@ use tokio_tungstenite::WebSocketStream;
2829use tokio_tungstenite:: tungstenite:: error:: Error as WebSocketError ;
2930use tokio_tungstenite:: tungstenite:: protocol:: Role ;
3031
31- use sush_common:: authn:: { AuthnError , Challenge , ChallengeResponse , Credentials } ;
32+ use sush_common:: authn:: { AuthnError , Challenge , ChallengeResponse , Credentials , Identity } ;
3233use sush_common:: interactive:: InteractiveSessionError ;
3334use sush_common:: jobs:: JobOutputStream :: { self , Stderr , Stdout } ;
3435use sush_common:: jobs:: {
@@ -65,6 +66,9 @@ const DEFAULT_CHUNK_SIZE: ByteSize = ByteSize::mib(32);
6566/// Default number of elements in a page of results.
6667const DEFAULT_PAGE_LIMIT : NonZeroU32 = NonZeroU32 :: new ( 100 ) . unwrap ( ) ;
6768
69+ /// Maximum number of authentication retries.
70+ const MAX_AUTHN_RETRIES : usize = 3 ;
71+
6872/// Default number of simultaneous downloads for large output.
6973const PARALLEL_CHUNKS : NonZeroU8 = NonZeroU8 :: new ( 8 ) . unwrap ( ) ;
7074
@@ -464,9 +468,52 @@ impl ClientCommand {
464468 }
465469}
466470
471+ async fn authz < E > (
472+ ctx : & mut impl CommandContext ,
473+ client : & Client ,
474+ ssh_auth_sock : & Option < String > ,
475+ ssh_key_id : & Option < KeyId > ,
476+ response : ResponseValue < E > ,
477+ ) -> Result < Identity , CommandError > {
478+ let mut ssh_agent = if let Some ( ssh_auth_sock) = ssh_auth_sock {
479+ SshAgentConnection :: connect ( ssh_auth_sock) . await ?
480+ } else {
481+ return Err ( CommandError :: MissingSshAuthSock ) ;
482+ } ;
483+ let public_key = ssh_agent. identity ( ssh_key_id. as_ref ( ) ) . await ?;
484+ let challenge = response
485+ . headers ( )
486+ . get ( WWW_AUTHENTICATE )
487+ . ok_or ( CommandError :: InvalidAuthorization ) ?
488+ . to_str ( )
489+ . map_err ( |_| CommandError :: InvalidAuthorization ) ?
490+ . parse :: < Challenge > ( ) ?;
491+ let response = ChallengeResponse :: new ( challenge) ;
492+ ctx. please_touch ( & public_key) ?;
493+ let signed = select ! {
494+ s = ssh_agent. sign( response) => s?,
495+ _ = ctrl_c( ) => return Err ( CommandError :: Canceled ) ,
496+ } ;
497+ let verified = signed. verify_with_ssh_public_key ( & public_key) ?;
498+ let credentials = Credentials :: new ( verified) ;
499+ Ok ( client
500+ . iam ( )
501+ . authorization ( credentials. to_string ( ) )
502+ . body ( public_key. to_string ( ) )
503+ . send ( )
504+ . await ?
505+ . into_inner ( ) )
506+ }
507+
467508macro_rules! with_authz {
468- ( $ctx: ident, $client: ident, $ssh_auth_sock: expr, $ssh_key_id: expr, $authz: ident => $req: expr) => {
509+ ( $ctx: ident, $client: ident, $ssh_auth_sock: expr, $ssh_key_id: expr, $authz: ident => $req: expr) => { {
510+ let mut i = 0 ;
469511 loop {
512+ i += 1 ;
513+ if i == MAX_AUTHN_RETRIES {
514+ return Err ( CommandError :: InvalidAuthorization ) ;
515+ }
516+
470517 let $authz = $ctx
471518 . get_identity( )
472519 . map( |i| i. to_owned( ) . into_credentials( ) . to_string( ) )
@@ -476,40 +523,13 @@ macro_rules! with_authz {
476523 Err ( ClientError :: ErrorResponse ( err) )
477524 if err. status( ) == StatusCode :: UNAUTHORIZED =>
478525 {
479- let mut ssh_agent = if let Some ( ssh_auth_sock) = $ssh_auth_sock {
480- SshAgentConnection :: connect( ssh_auth_sock) . await ?
481- } else {
482- return Err ( CommandError :: MissingSshAuthSock ) ;
483- } ;
484- let public_key = ssh_agent. identity( $ssh_key_id) . await ?;
485- let challenge = err
486- . headers( )
487- . get( WWW_AUTHENTICATE )
488- . ok_or( CommandError :: InvalidAuthorization ) ?
489- . to_str( )
490- . map_err( |_| CommandError :: InvalidAuthorization ) ?
491- . parse:: <Challenge >( ) ?;
492- let response = ChallengeResponse :: new( challenge) ;
493- $ctx. please_touch( & public_key) ?;
494- let signed = select! {
495- s = ssh_agent. sign( response) => s?,
496- _ = ctrl_c( ) => return Err ( CommandError :: Canceled ) ,
497- } ;
498- let verified = signed. verify_with_ssh_public_key( & public_key) ?;
499- let credentials = Credentials :: new( verified) ;
500- let identity = $client
501- . iam( )
502- . authorization( credentials. to_string( ) )
503- . body( public_key. to_string( ) )
504- . send( )
505- . await ?
506- . into_inner( ) ;
526+ let identity = authz( $ctx, $client, $ssh_auth_sock, $ssh_key_id, err) . await ?;
507527 $ctx. set_identity( Some ( identity) ) ;
508528 }
509529 res => break res,
510530 }
511531 }
512- } ;
532+ } } ;
513533}
514534
515535async fn iam (
@@ -543,7 +563,7 @@ async fn iam(
543563 ctx,
544564 client,
545565 ssh_auth_sock,
546- ssh_key_id. as_ref ( ) ,
566+ ssh_key_id,
547567 authz => client
548568 . identities( )
549569 . limit( limit)
@@ -562,7 +582,7 @@ async fn iam(
562582 ctx,
563583 client,
564584 ssh_auth_sock,
565- ssh_key_id. as_ref ( ) ,
585+ ssh_key_id,
566586 authz => client
567587 . identities( )
568588 . authorization( & authz)
@@ -578,16 +598,16 @@ async fn iam(
578598 }
579599
580600 IdentityCommand :: Login => {
581- ctx . set_identity ( None ) ;
582- let identity = with_authz ! (
583- ctx ,
584- client,
585- ssh_auth_sock ,
586- ssh_key_id . as_ref ( ) ,
587- authz => client . iam ( ) . authorization ( & authz ) . body ( None ) . send ( )
588- ) ?
589- . into_inner ( ) ;
590- ctx . iam ( & identity )
601+ if let Err ( ClientError :: ErrorResponse ( err ) ) = client . identities ( ) . send ( ) . await
602+ && err . status ( ) == StatusCode :: UNAUTHORIZED
603+ {
604+ let identity = authz ( ctx , client, ssh_auth_sock , ssh_key_id , err ) . await ? ;
605+ ctx . iam ( & identity ) ? ;
606+ ctx . set_identity ( Some ( identity ) ) ;
607+ Ok ( ( ) )
608+ } else {
609+ Err ( CommandError :: InvalidAuthorization )
610+ }
591611 }
592612
593613 IdentityCommand :: Revoke { revoke } => {
@@ -596,7 +616,7 @@ async fn iam(
596616 ctx,
597617 client,
598618 ssh_auth_sock,
599- ssh_key_id. as_ref ( ) ,
619+ ssh_key_id,
600620 authz => client
601621 . revoke_identity( )
602622 . authorization( & authz)
@@ -625,7 +645,7 @@ async fn cert(
625645 ctx,
626646 client,
627647 ssh_auth_sock,
628- ssh_key_id. as_ref ( ) ,
648+ ssh_key_id,
629649 authz => client
630650 . import_cert( )
631651 . authorization( authz)
@@ -641,7 +661,7 @@ async fn cert(
641661 ctx,
642662 client,
643663 ssh_auth_sock,
644- ssh_key_id. as_ref ( ) ,
664+ ssh_key_id,
645665 authz => client
646666 . cert_chain( )
647667 . authorization( authz)
@@ -667,7 +687,7 @@ async fn session(
667687 ctx,
668688 client,
669689 ssh_auth_sock,
670- ssh_key_id. as_ref ( ) ,
690+ ssh_key_id,
671691 authz => client
672692 . session_start( )
673693 . authorization( & authz)
@@ -697,7 +717,7 @@ async fn session(
697717 ctx,
698718 client,
699719 ssh_auth_sock,
700- ssh_key_id. as_ref ( ) ,
720+ ssh_key_id,
701721 authz => client
702722 . session_stop( )
703723 . session_id( session_id. clone( ) )
@@ -804,7 +824,7 @@ async fn job(
804824 ctx,
805825 client,
806826 ssh_auth_sock,
807- ssh_key_id. as_ref ( ) ,
827+ ssh_key_id,
808828 authz => client
809829 . job_stop( )
810830 . job_id( & job_id)
@@ -819,7 +839,7 @@ async fn job(
819839 ctx,
820840 client,
821841 ssh_auth_sock,
822- ssh_key_id. as_ref ( ) ,
842+ ssh_key_id,
823843 authz => client
824844 . job_status( )
825845 . job_id( & job_id)
@@ -1028,7 +1048,7 @@ async fn job_output(
10281048 ctx,
10291049 client,
10301050 ssh_auth_sock,
1031- ssh_key_id. as_ref ( ) ,
1051+ ssh_key_id,
10321052 authz => client
10331053 . job_output_delete( )
10341054 . authorization( & authz)
@@ -1051,7 +1071,7 @@ async fn job_output(
10511071 ctx,
10521072 client,
10531073 ssh_auth_sock,
1054- ssh_key_id. as_ref ( ) ,
1074+ ssh_key_id,
10551075 authz => client
10561076 . job_status( )
10571077 . authorization( authz)
@@ -1144,7 +1164,7 @@ async fn job_output(
11441164 ctx,
11451165 client,
11461166 ssh_auth_sock,
1147- ssh_key_id. as_ref ( ) ,
1167+ ssh_key_id,
11481168 authz => client
11491169 . job_output( )
11501170 . authorization( authz)
@@ -1235,7 +1255,7 @@ async fn job_start_interactive_session(
12351255 ctx,
12361256 client,
12371257 ssh_auth_sock,
1238- ssh_key_id. as_ref ( ) ,
1258+ ssh_key_id,
12391259 authz => client
12401260 . job_start_interactive_session( )
12411261 . authorization( & authz)
0 commit comments