@@ -22,6 +22,13 @@ class Config
2222 */
2323 private static $ tempFiles = [];
2424
25+ /**
26+ * Path to the operative config file
27+ *
28+ * @var string
29+ */
30+ private $ path ;
31+
2532 /**
2633 * Server URI (ie: https://host)
2734 *
@@ -99,6 +106,13 @@ class Config
99106 */
100107 private $ isAuthProvider = false ;
101108
109+ /**
110+ * If the user auth data is generated via an exec provider
111+ *
112+ * @var bool
113+ */
114+ private $ isExecProvider = false ;
115+
102116 /**
103117 * Data from parsed config file
104118 *
@@ -204,6 +218,7 @@ public static function BuildConfigFromFile($path = null, $contextName = null)
204218 }
205219
206220 $ config = new Config ();
221+ $ config ->setPath ($ path );
207222 $ config ->setParsedConfigFile ($ yaml );
208223 $ config ->useContext ($ contextName );
209224
@@ -281,9 +296,17 @@ public function useContext($contextName)
281296 $ this ->setToken ($ user ['token ' ]);
282297 }
283298
299+ // should never have both auth-provider and exec at the same time
300+
284301 if (!empty ($ user ['auth-provider ' ])) {
285302 $ this ->setIsAuthProvider (true );
286303 }
304+
305+ if (!empty ($ user ['exec ' ])) {
306+ $ this ->setIsExecProvider (true );
307+ // we pre-emptively invoke this in this case
308+ $ this ->getExecProviderAuth ();
309+ }
287310 }
288311
289312 /**
@@ -297,6 +320,31 @@ protected function resetAuthData()
297320 $ this ->setExpiry (null );
298321 $ this ->setToken (null );
299322 $ this ->setIsAuthProvider (false );
323+ $ this ->setIsExecProvider (false );
324+ }
325+
326+ /**
327+ * Set path
328+ *
329+ * @param $path
330+ */
331+ public function setPath ($ path )
332+ {
333+ if (!empty ($ path )) {
334+ $ path = realpath (($ path ));
335+ }
336+
337+ $ this ->path = $ path ;
338+ }
339+
340+ /**
341+ * Get path
342+ *
343+ * @return string
344+ */
345+ public function getPath ()
346+ {
347+ return $ this ->path ;
300348 }
301349
302350 /**
@@ -429,6 +477,14 @@ public function getToken()
429477 }
430478 }
431479
480+ // only do this if token is present to begin with
481+ if ($ this ->getIsExecProvider () && !empty ($ this ->token )) {
482+ // set token if expired
483+ if ($ this ->getExpiry () && time () >= $ this ->getExpiry ()) {
484+ $ this ->getExecProviderAuth ();
485+ }
486+ }
487+
432488 return $ this ->token ;
433489 }
434490
@@ -481,6 +537,83 @@ protected function getAuthProviderToken()
481537 }
482538 }
483539
540+ /**
541+ * @link https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
542+ * @link https://banzaicloud.com/blog/kubeconfig-security/#exec-helper
543+ *
544+ * Set the auth data using the exec provider
545+ */
546+ protected function getExecProviderAuth ()
547+ {
548+ $ user = $ this ->getUser ();
549+ $ path = $ this ->getPath ();
550+
551+ $ command = $ user ['exec ' ]['command ' ];
552+
553+ // relative commands should be executed relative to the directory holding the config file
554+ if (substr ($ command , 0 , 1 ) == ". " ) {
555+ $ dir = dirname ($ path );
556+ $ command = $ dir . substr ($ command , 1 );
557+ }
558+
559+ // add args
560+ if (!empty ($ user ['exec ' ]['args ' ])) {
561+ foreach ($ user ['exec ' ]['args ' ] as $ arg ) {
562+ $ command .= " " . $ arg ;
563+ }
564+ }
565+
566+ // set env
567+ // beware this sets the env var for the whole process indefinitely
568+ if (!empty ($ user ['exec ' ]['env ' ])) {
569+ foreach ($ user ['exec ' ]['env ' ] as $ env ) {
570+ putenv ("$ {env['name ' ]}= $ {env['value ' ]}" );
571+ }
572+ }
573+
574+ // execute command and store output
575+ $ output = [];
576+ $ exit_code = null ;
577+ exec ($ command , $ output , $ exit_code );
578+ $ output = implode ("\n" , $ output );
579+
580+ if ($ exit_code !== 0 ) {
581+ throw new \Error ("error executing access token command \"$ {command}\": $ {output}" );
582+ } else {
583+ $ output = json_decode ($ output , true );
584+ }
585+
586+ if (!is_array ($ output )) {
587+ throw new \Error ("error retrieving token: auth exec failed to return valid data " );
588+ }
589+
590+ if ($ output ["kind " ] != "ExecCredential " ) {
591+ throw new \Error ("error retrieving auth: auth exec failed to return valid data " );
592+ }
593+
594+ if ($ output ['apiVersion ' ] != 'client.authentication.k8s.io/v1beta1 ' ) {
595+ throw new \Error ("error retrieving auth: auth exec unsupported apiVersion " );
596+ }
597+
598+ if (!empty ($ output ['status ' ]['clientCertificateData ' ])) {
599+ $ path = self ::writeTempFile ($ output ['status ' ]['clientCertificateData ' ]);
600+ $ this ->setClientCertificatePath ($ path );
601+ }
602+
603+ if (!empty ($ output ['status ' ]['clientKeyData ' ])) {
604+ $ path = self ::writeTempFile ($ output ['status ' ]['clientKeyData ' ]);
605+ $ this ->setClientKeyPath ($ path );
606+ }
607+
608+ if (!empty ($ output ['status ' ]['expirationTimestamp ' ])) {
609+ $ this ->setExpiry ($ output ['status ' ]['expirationTimestamp ' ]);
610+ }
611+
612+ if (!empty ($ output ['status ' ]['token ' ])) {
613+ $ this ->setToken ($ output ['status ' ]['token ' ]);
614+ }
615+ }
616+
484617 /**
485618 * Set expiry
486619 *
@@ -587,6 +720,26 @@ public function getIsAuthProvider()
587720 return $ this ->isAuthProvider ;
588721 }
589722
723+ /**
724+ * Set if user credentials use exec provider
725+ *
726+ * @param $v bool
727+ */
728+ public function setIsExecProvider ($ v )
729+ {
730+ $ this ->isExecProvider = $ v ;
731+ }
732+
733+ /**
734+ * Get if user credentials use exec provider
735+ *
736+ * @return bool
737+ */
738+ public function getIsExecProvider ()
739+ {
740+ return $ this ->isExecProvider ;
741+ }
742+
590743 /**
591744 * Set the data of the parsed config file
592745 *
0 commit comments