@@ -222,59 +222,9 @@ public String getAccessToken() {
222
222
}
223
223
}
224
224
}
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 ;
278
228
}
279
229
if (currentUser .containsKey ("token" )) {
280
230
return (String ) currentUser .get ("token" );
@@ -291,6 +241,66 @@ public String getAccessToken() {
291
241
return null ;
292
242
}
293
243
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
+
294
304
public boolean verifySSL () {
295
305
if (currentCluster == null ) {
296
306
return false ;
0 commit comments