@@ -222,59 +222,9 @@ public String getAccessToken() {
222222 }
223223 }
224224 }
225- Object exec = currentUser .get ("exec" );
226- if (exec != null ) {
227- // TODO extract to helper method for clarity
228- Map <String , Object > execMap = (Map <String , Object >) exec ;
229- // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
230- String apiVersion = (String ) execMap .get ("apiVersion" );
231- if ("client.authentication.k8s.io/v1beta1" .equals (apiVersion )) { // TODO or v1alpha1 is apparently identical and could be supported
232- String command = (String ) execMap .get ("command" );
233- List <Map <String , String >> env = (List ) execMap .get ("env" );
234- List <String > args = (List ) execMap .get ("args" );
235- // TODO relativize command to basedir of config file (requires KubeConfig to be given a basedir)
236- List <String > argv = new ArrayList <>();
237- argv .add (command );
238- if (args != null ) {
239- argv .addAll (args );
240- }
241- ProcessBuilder pb = new ProcessBuilder (argv );
242- if (env != null ) {
243- // TODO apply
244- }
245- pb .redirectError (ProcessBuilder .Redirect .INHERIT );
246- try {
247- Process proc = pb .start ();
248- JsonElement root = null ;
249- try (InputStream is = proc .getInputStream ();
250- Reader r = new InputStreamReader (is , StandardCharsets .UTF_8 )) {
251- root = new JsonParser ().parse (r );
252- } catch (JsonParseException x ) {
253- log .error ("Failed to parse output of " + command , x );
254- }
255- int r = proc .waitFor ();
256- if (r == 0 ) {
257- if (root != null ) {
258- // TODO verify .apiVersion and .kind = ExecCredential
259- JsonObject status = root .getAsJsonObject ().get ("status" ).getAsJsonObject ();
260- JsonElement token = status .get ("token" );
261- if (token != null ) {
262- return token .getAsString ();
263- }
264- // TODO handle clientCertificateData/clientKeyData (KubeconfigAuthentication is not yet set up for that to be dynamic)
265- }
266- } else {
267- log .error ("{} failed with exit code {}" , command , r );
268- }
269- } catch (IOException | InterruptedException x ) {
270- log .error ("Failed to run " + command , x );
271- }
272- // TODO cache tokens between calls, up to .status.expirationTimestamp
273- // TODO a 401 is supposed to force a refresh, but KubeconfigAuthentication hard-codes AccessTokenAuthentication which does not support that
274- // and anyway ClientBuilder only calls Authenticator.provide once per ApiClient; we would need to do it on every request
275- } else {
276- log .error ("Unrecognized user.exec.apiVersion: {}" , apiVersion );
277- }
225+ String tokenViaExecCredential = tokenViaExecCredential ((Map <String , Object >) currentUser .get ("exec" ));
226+ if (tokenViaExecCredential != null ) {
227+ return tokenViaExecCredential ;
278228 }
279229 if (currentUser .containsKey ("token" )) {
280230 return (String ) currentUser .get ("token" );
@@ -291,6 +241,66 @@ public String getAccessToken() {
291241 return null ;
292242 }
293243
244+ /**
245+ * Attempt to create an access token by running a configured external program.
246+ * @see <a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins">Authenticating » client-go credential plugins</a>
247+ */
248+ private String tokenViaExecCredential (Map <String , Object > execMap ) {
249+ if (execMap == null ) {
250+ return null ;
251+ }
252+ String apiVersion = (String ) execMap .get ("apiVersion" );
253+ if (!"client.authentication.k8s.io/v1beta1" .equals (apiVersion )) { // TODO or v1alpha1 is apparently identical and could be supported
254+ log .error ("Unrecognized user.exec.apiVersion: {}" , apiVersion );
255+ return null ;
256+ }
257+ String command = (String ) execMap .get ("command" );
258+ List <Map <String , String >> env = (List ) execMap .get ("env" );
259+ List <String > args = (List ) execMap .get ("args" );
260+ // TODO relativize command to basedir of config file (requires KubeConfig to be given a basedir)
261+ List <String > argv = new ArrayList <>();
262+ argv .add (command );
263+ if (args != null ) {
264+ argv .addAll (args );
265+ }
266+ ProcessBuilder pb = new ProcessBuilder (argv );
267+ if (env != null ) {
268+ // TODO apply
269+ }
270+ pb .redirectError (ProcessBuilder .Redirect .INHERIT );
271+ try {
272+ Process proc = pb .start ();
273+ JsonElement root ;
274+ try (InputStream is = proc .getInputStream ();
275+ Reader r = new InputStreamReader (is , StandardCharsets .UTF_8 )) {
276+ root = new JsonParser ().parse (r );
277+ } catch (JsonParseException x ) {
278+ log .error ("Failed to parse output of " + command , x );
279+ return null ;
280+ }
281+ int r = proc .waitFor ();
282+ if (r != 0 ) {
283+ log .error ("{} failed with exit code {}" , command , r );
284+ return null ;
285+ }
286+ // TODO verify .apiVersion and .kind = ExecCredential
287+ JsonObject status = root .getAsJsonObject ().get ("status" ).getAsJsonObject ();
288+ JsonElement token = status .get ("token" );
289+ if (token == null ) {
290+ // TODO handle clientCertificateData/clientKeyData (KubeconfigAuthentication is not yet set up for that to be dynamic)
291+ log .warn ("No token produced by {}" , command );
292+ return null ;
293+ }
294+ return token .getAsString ();
295+ } catch (IOException | InterruptedException x ) {
296+ log .error ("Failed to run " + command , x );
297+ return null ;
298+ }
299+ // TODO cache tokens between calls, up to .status.expirationTimestamp
300+ // TODO a 401 is supposed to force a refresh, but KubeconfigAuthentication hard-codes AccessTokenAuthentication which does not support that
301+ // and anyway ClientBuilder only calls Authenticator.provide once per ApiClient; we would need to do it on every request
302+ }
303+
294304 public boolean verifySSL () {
295305 if (currentCluster == null ) {
296306 return false ;
0 commit comments