@@ -442,20 +442,24 @@ fn token_from_gcp_provider(provider: &AuthProviderConfig) -> Result<ProviderToke
442442 // NB: This property does currently not exist upstream in client-go
443443 // See https://github.com/kube-rs/kube/issues/1060
444444 let drop_env = provider. config . get ( "cmd-drop-env" ) . cloned ( ) . unwrap_or_default ( ) ;
445- // TODO splitting args by space is not safe
445+ let drop_env = shell_words:: split ( & drop_env)
446+ . map_err ( |e| Error :: AuthExec ( format ! ( "invalid cmd-drop-env: {e}" ) ) ) ?;
447+ let params = shell_words:: split ( & params)
448+ . map_err ( |e| Error :: AuthExec ( format ! ( "invalid cmd-args: {e}" ) ) ) ?;
449+ let command_line = params. join ( " " ) ;
446450 let mut command = Command :: new ( cmd) ;
447451 // Do not pass the following env vars to the command
448- for env in drop_env. trim ( ) . split ( ' ' ) {
452+ for env in drop_env {
449453 command. env_remove ( env) ;
450454 }
451455 let output = command
452- . args ( params. trim ( ) . split ( ' ' ) )
456+ . args ( params)
453457 . output ( )
454458 . map_err ( |e| Error :: AuthExec ( format ! ( "Executing {cmd:} failed: {e:?}" ) ) ) ?;
455459
456460 if !output. status . success ( ) {
457461 return Err ( Error :: AuthExecRun {
458- cmd : format ! ( "{cmd} {params }" ) ,
462+ cmd : format ! ( "{cmd} {command_line }" ) ,
459463 status : output. status ,
460464 out : output,
461465 } ) ;
@@ -646,43 +650,25 @@ fn auth_exec(auth: &ExecConfig) -> Result<ExecCredential, Error> {
646650
647651#[ cfg( test) ]
648652mod test {
649- use crate :: config:: Kubeconfig ;
650653 use std:: time:: { Duration , Instant } ;
651654
652655 use super :: * ;
653656
654657 /// Build an `AuthInfo` whose gcp auth-provider runs `cmd_path cmd_args` to emit credential JSON.
655658 fn gcp_auth_info ( cmd_path : & str , cmd_args : & str ) -> AuthInfo {
656- let test_file = format ! (
657- r#"
658- apiVersion: v1
659- clusters:
660- - cluster:
661- certificate-authority-data: XXXXXXX
662- server: https://36.XXX.XXX.XX
663- name: generic-name
664- contexts:
665- - context:
666- cluster: generic-name
667- user: generic-name
668- name: generic-name
669- current-context: generic-name
670- kind: Config
671- preferences: {{}}
672- users:
673- - name: generic-name
674- user:
675- auth-provider:
676- config:
677- cmd-args: {cmd_args}
678- cmd-path: {cmd_path}
679- expiry-key: '{{.credential.token_expiry}}'
680- token-key: '{{.credential.access_token}}'
681- name: gcp
682- "#
683- ) ;
684- let config: Kubeconfig = serde_saphyr:: from_str ( & test_file) . unwrap ( ) ;
685- config. auth_infos [ 0 ] . auth_info . clone ( ) . unwrap ( )
659+ AuthInfo {
660+ auth_provider : Some ( AuthProviderConfig {
661+ name : "gcp" . into ( ) ,
662+ config : std:: collections:: HashMap :: from ( [
663+ ( "cmd-args" . into ( ) , cmd_args. into ( ) ) ,
664+ ( "cmd-path" . into ( ) , cmd_path. into ( ) ) ,
665+ ( "expiry-key" . into ( ) , "{.credential.token_expiry}" . into ( ) ) ,
666+ ( "token-key" . into ( ) , "{.credential.access_token}" . into ( ) ) ,
667+ ] ) ,
668+ ..Default :: default ( )
669+ } ) ,
670+ ..Default :: default ( )
671+ }
686672 }
687673
688674 fn cred_json ( token : & str , expiry : & str ) -> String {
@@ -695,7 +681,7 @@ mod test {
695681 #[ ignore = "fails on windows mysteriously" ]
696682 async fn exec_auth_command ( ) -> Result < ( ) , Error > {
697683 let expiry = ( Timestamp :: now ( ) + SIXTY_SEC ) . to_string ( ) ;
698- let auth_info = gcp_auth_info ( "echo " , & format ! ( "'{}'" , cred_json( "my_token" , & expiry) ) ) ;
684+ let auth_info = gcp_auth_info ( "printf " , & format ! ( "%s '{}'" , cred_json( "my_token" , & expiry) ) ) ;
699685 match Auth :: try_from ( & auth_info) . unwrap ( ) {
700686 Auth :: RefreshableToken ( RefreshableToken :: Exec ( refreshable) ) => {
701687 let ( token, _expire, info) = Arc :: try_unwrap ( refreshable) . unwrap ( ) . into_inner ( ) ;
@@ -712,7 +698,7 @@ mod test {
712698 #[ ignore = "shells out to echo/sh; skipped on windows" ]
713699 async fn exec_token_refresh_via_to_header ( ) -> Result < ( ) , Error > {
714700 let fresh_expiry = ( Timestamp :: now ( ) + SignedDuration :: from_secs ( 3600 ) ) . to_string ( ) ;
715- let auth_info = gcp_auth_info ( "echo " , & format ! ( "'{}'" , cred_json( "my_token" , & fresh_expiry) ) ) ;
701+ let auth_info = gcp_auth_info ( "printf " , & format ! ( "%s '{}'" , cred_json( "my_token" , & fresh_expiry) ) ) ;
716702 // Seed with a stale token + past expiry to force the refresh branch.
717703 let stale_expiry = Timestamp :: now ( ) - SIXTY_SEC ;
718704 let refreshable = RefreshableToken :: Exec ( Arc :: new ( Mutex :: new ( (
@@ -773,6 +759,28 @@ mod test {
773759 Ok ( ( ) )
774760 }
775761
762+ #[ tokio:: test]
763+ #[ ignore = "shells out to sh; skipped on windows" ]
764+ async fn exec_auth_command_parses_quoted_cmd_args ( ) -> Result < ( ) , Error > {
765+ let auth_info = AuthInfo {
766+ auth_provider : Some ( AuthProviderConfig {
767+ name : "gcp" . into ( ) ,
768+ config : std:: collections:: HashMap :: from ( [
769+ ( "cmd-path" . into ( ) , "sh" . into ( ) ) ,
770+ ( "cmd-args" . into ( ) , "-c 'printf %s \" my token\" '" . into ( ) ) ,
771+ ] ) ,
772+ ..Default :: default ( )
773+ } ) ,
774+ ..Default :: default ( )
775+ } ;
776+
777+ match Auth :: try_from ( & auth_info) . unwrap ( ) {
778+ Auth :: Bearer ( token) => assert_eq ! ( token. expose_secret( ) , "my token" ) ,
779+ _ => unreachable ! ( ) ,
780+ }
781+ Ok ( ( ) )
782+ }
783+
776784 #[ test]
777785 fn token_file ( ) {
778786 let file = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
0 commit comments