1
1
using System ;
2
2
using System . Collections . Generic ;
3
- #if NETSTANDARD2_0
4
3
using Newtonsoft . Json ;
5
4
using System . Diagnostics ;
6
- #endif
7
5
using System . IO ;
8
6
using System . Linq ;
9
7
using System . Runtime . InteropServices ;
@@ -378,7 +376,6 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext)
378
376
}
379
377
}
380
378
381
- #if NETSTANDARD2_0
382
379
if ( userDetails . UserCredentials . ExternalExecution != null )
383
380
{
384
381
if ( string . IsNullOrWhiteSpace ( userDetails . UserCredentials . ExternalExecution . Command ) )
@@ -392,12 +389,16 @@ private void SetUserDetails(K8SConfiguration k8SConfig, Context activeContext)
392
389
throw new KubeConfigException ( "External command execution missing ApiVersion key" ) ;
393
390
}
394
391
395
- var token = ExecuteExternalCommand ( userDetails . UserCredentials . ExternalExecution ) ;
396
- AccessToken = token ;
392
+ var ( accessToken , clientCertificateData , clientCertificateKeyData ) = ExecuteExternalCommand ( userDetails . UserCredentials . ExternalExecution ) ;
393
+ AccessToken = accessToken ;
394
+ // When reading ClientCertificateData from a config file it will be base64 encoded, and code later in the system (see CertUtils.GeneratePfx)
395
+ // expects ClientCertificateData and ClientCertificateKeyData to be base64 encoded because of this. However the string returned by external
396
+ // auth providers is the raw certificate and key PEM text, so we need to take that and base64 encoded it here so it can be decoded later.
397
+ ClientCertificateData = clientCertificateData == null ? null : Convert . ToBase64String ( System . Text . Encoding . ASCII . GetBytes ( clientCertificateData ) ) ;
398
+ ClientCertificateKeyData = clientCertificateKeyData == null ? null : Convert . ToBase64String ( System . Text . Encoding . ASCII . GetBytes ( clientCertificateKeyData ) ) ;
397
399
398
400
userCredentialsFound = true ;
399
401
}
400
- #endif
401
402
402
403
if ( ! userCredentialsFound )
403
404
{
@@ -411,17 +412,7 @@ public static string RenewAzureToken(string tenantId, string clientId, string ap
411
412
throw new KubeConfigException ( "Refresh not supported." ) ;
412
413
}
413
414
414
- #if NETSTANDARD2_0
415
- /// <summary>
416
- /// Implementation of the proposal for out-of-tree client
417
- /// authentication providers as described here --
418
- /// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md
419
- /// Took inspiration from python exec_provider.py --
420
- /// https://github.com/kubernetes-client/python-base/blob/master/config/exec_provider.py
421
- /// </summary>
422
- /// <param name="config">The external command execution configuration</param>
423
- /// <returns>The token received from the external command execution</returns>
424
- public static string ExecuteExternalCommand ( ExternalExecution config )
415
+ public static Process CreateRunnableExternalProcess ( ExternalExecution config )
425
416
{
426
417
var execInfo = new Dictionary < string , dynamic >
427
418
{
@@ -432,15 +423,22 @@ public static string ExecuteExternalCommand(ExternalExecution config)
432
423
433
424
var process = new Process ( ) ;
434
425
435
- process . StartInfo . Environment . Add ( "KUBERNETES_EXEC_INFO" ,
436
- JsonConvert . SerializeObject ( execInfo ) ) ;
437
-
426
+ process . StartInfo . EnvironmentVariables . Add ( "KUBERNETES_EXEC_INFO" , JsonConvert . SerializeObject ( execInfo ) ) ;
438
427
if ( config . EnvironmentVariables != null )
439
428
{
440
- foreach ( var configEnvironmentVariableKey in config . EnvironmentVariables . Keys )
429
+ foreach ( var configEnvironmentVariable in config . EnvironmentVariables )
441
430
{
442
- process . StartInfo . Environment . Add ( key : configEnvironmentVariableKey ,
443
- value : config . EnvironmentVariables [ configEnvironmentVariableKey ] ) ;
431
+ if ( configEnvironmentVariable . ContainsKey ( "name" ) && configEnvironmentVariable . ContainsKey ( "value" ) )
432
+ {
433
+ process . StartInfo . EnvironmentVariables . Add (
434
+ configEnvironmentVariable [ "name" ] ,
435
+ configEnvironmentVariable [ "value" ] ) ;
436
+ }
437
+ else
438
+ {
439
+ var badVariable = string . Join ( "," , configEnvironmentVariable . Select ( x => $ "{ x . Key } ={ x . Value } ") ) ;
440
+ throw new KubeConfigException ( $ "Invalid environment variable defined: { badVariable } ") ;
441
+ }
444
442
}
445
443
}
446
444
@@ -454,6 +452,24 @@ public static string ExecuteExternalCommand(ExternalExecution config)
454
452
process . StartInfo . RedirectStandardError = true ;
455
453
process . StartInfo . UseShellExecute = false ;
456
454
455
+ return process ;
456
+ }
457
+
458
+ /// <summary>
459
+ /// Implementation of the proposal for out-of-tree client
460
+ /// authentication providers as described here --
461
+ /// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md
462
+ /// Took inspiration from python exec_provider.py --
463
+ /// https://github.com/kubernetes-client/python-base/blob/master/config/exec_provider.py
464
+ /// </summary>
465
+ /// <param name="config">The external command execution configuration</param>
466
+ /// <returns>
467
+ /// The token, client certificate data, and the client key data received from the external command execution
468
+ /// </returns>
469
+ public static ( string , string , string ) ExecuteExternalCommand ( ExternalExecution config )
470
+ {
471
+ var process = CreateRunnableExternalProcess ( config ) ;
472
+
457
473
try
458
474
{
459
475
process . Start ( ) ;
@@ -482,7 +498,23 @@ public static string ExecuteExternalCommand(ExternalExecution config)
482
498
$ "external exec failed because api version { responseObject . ApiVersion } does not match { config . ApiVersion } ") ;
483
499
}
484
500
485
- return responseObject . Status [ "token" ] ;
501
+ if ( responseObject . Status . ContainsKey ( "token" ) )
502
+ {
503
+ return ( responseObject . Status [ "token" ] , null , null ) ;
504
+ }
505
+ else if ( responseObject . Status . ContainsKey ( "clientCertificateData" ) )
506
+ {
507
+ if ( ! responseObject . Status . ContainsKey ( "clientKeyData" ) )
508
+ {
509
+ throw new KubeConfigException ( $ "external exec failed missing clientKeyData field in plugin output") ;
510
+ }
511
+
512
+ return ( null , responseObject . Status [ "clientCertificateData" ] , responseObject . Status [ "clientKeyData" ] ) ;
513
+ }
514
+ else
515
+ {
516
+ throw new KubeConfigException ( $ "external exec failed missing token or clientCertificateData field in plugin output") ;
517
+ }
486
518
}
487
519
catch ( JsonSerializationException ex )
488
520
{
@@ -493,7 +525,6 @@ public static string ExecuteExternalCommand(ExternalExecution config)
493
525
throw new KubeConfigException ( $ "external exec failed due to uncaught exception: { ex } ") ;
494
526
}
495
527
}
496
- #endif
497
528
498
529
/// <summary>
499
530
/// Loads entire Kube Config from default or explicit file path
0 commit comments