Skip to content

Commit 014685f

Browse files
committed
support exec style user configuration
1 parent f377fed commit 014685f

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

src/KubernetesClient/Config.php

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)