Add option to refresh gcp token when config is cmd-path
This commit is contained in:
parent
a2d1024524
commit
39113de2aa
@ -20,8 +20,10 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from collections import namedtuple
|
||||
|
||||
import google.auth
|
||||
import google.auth.transport.requests
|
||||
@ -133,6 +135,46 @@ class FileOrData(object):
|
||||
return self._data
|
||||
|
||||
|
||||
class CommandTokenSource(object):
|
||||
def __init__(self, cmd, args, tokenKey, expiryKey):
|
||||
self._cmd = cmd
|
||||
self._args = args
|
||||
if not tokenKey:
|
||||
self._tokenKey = '{.access_token}'
|
||||
else:
|
||||
self._tokenKey = tokenKey
|
||||
if not expiryKey:
|
||||
self._expiryKey = '{.token_expiry}'
|
||||
else:
|
||||
self._expiryKey = expiryKey
|
||||
|
||||
def token(self):
|
||||
fullCmd = self._cmd + (" ") + " ".join(self._args)
|
||||
process = subprocess.Popen(
|
||||
[self._cmd] + self._args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
(stdout, stderr) = process.communicate()
|
||||
exit_code = process.wait()
|
||||
if exit_code != 0:
|
||||
msg = 'cmd-path: process returned %d' % exit_code
|
||||
msg += "\nCmd: %s" % fullCmd
|
||||
stderr = stderr.strip()
|
||||
if stderr:
|
||||
msg += '\nStderr: %s' % stderr
|
||||
raise ConfigException(msg)
|
||||
try:
|
||||
data = json.loads(stdout)
|
||||
except ValueError as de:
|
||||
raise ConfigException(
|
||||
'exec: failed to decode process output: %s' % de)
|
||||
A = namedtuple('A', ['token', 'expiry'])
|
||||
return A(
|
||||
token=data['credential']['access_token'],
|
||||
expiry=parse_rfc3339(data['credential']['token_expiry']))
|
||||
|
||||
|
||||
class KubeConfigLoader(object):
|
||||
|
||||
def __init__(self, config_dict, active_context=None,
|
||||
@ -156,7 +198,38 @@ class KubeConfigLoader(object):
|
||||
self._config_base_path = config_base_path
|
||||
self._config_persister = config_persister
|
||||
|
||||
def _refresh_credentials_with_cmd_path():
|
||||
config = self._user['auth-provider']['config']
|
||||
cmd = config['cmd-path']
|
||||
if len(cmd) == 0:
|
||||
raise ConfigException(
|
||||
'missing access token cmd '
|
||||
'(cmd-path is an empty string in your kubeconfig file)')
|
||||
if 'scopes' in config and config['scopes'] != "":
|
||||
raise ConfigException(
|
||||
'scopes can only be used '
|
||||
'when kubectl is using a gcp service account key')
|
||||
args = []
|
||||
if 'cmd-args' in config:
|
||||
args = config['cmd-args'].split()
|
||||
else:
|
||||
fields = config['cmd-path'].split()
|
||||
cmd = fields[0]
|
||||
args = fields[1:]
|
||||
|
||||
commandTokenSource = CommandTokenSource(
|
||||
cmd, args,
|
||||
config.safe_get('token-key'),
|
||||
config.safe_get('expiry-key'))
|
||||
return commandTokenSource.token()
|
||||
|
||||
def _refresh_credentials():
|
||||
# Refresh credentials using cmd-path
|
||||
if ('auth-provider' in self._user and
|
||||
'config' in self._user['auth-provider'] and
|
||||
'cmd-path' in self._user['auth-provider']['config']):
|
||||
return _refresh_credentials_with_cmd_path()
|
||||
|
||||
credentials, project_id = google.auth.default(scopes=[
|
||||
'https://www.googleapis.com/auth/cloud-platform',
|
||||
'https://www.googleapis.com/auth/userinfo.email'
|
||||
|
||||
@ -19,6 +19,7 @@ import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from collections import namedtuple
|
||||
|
||||
import mock
|
||||
import yaml
|
||||
@ -27,9 +28,11 @@ from six import PY3, next
|
||||
from kubernetes.client import Configuration
|
||||
|
||||
from .config_exception import ConfigException
|
||||
from .kube_config import (ENV_KUBECONFIG_PATH_SEPARATOR, ConfigNode,
|
||||
FileOrData, KubeConfigLoader, KubeConfigMerger,
|
||||
_cleanup_temp_files, _create_temp_file_with_content,
|
||||
from .dateutil import parse_rfc3339
|
||||
from .kube_config import (ENV_KUBECONFIG_PATH_SEPARATOR, CommandTokenSource,
|
||||
ConfigNode, FileOrData, KubeConfigLoader,
|
||||
KubeConfigMerger, _cleanup_temp_files,
|
||||
_create_temp_file_with_content,
|
||||
list_kube_config_contexts, load_kube_config,
|
||||
new_client_from_config)
|
||||
|
||||
@ -550,6 +553,27 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
"user": "exec_cred_user"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "contexttestcmdpath",
|
||||
"context": {
|
||||
"cluster": "clustertestcmdpath",
|
||||
"user": "usertestcmdpath"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "contexttestcmdpathempty",
|
||||
"context": {
|
||||
"cluster": "clustertestcmdpath",
|
||||
"user": "usertestcmdpathempty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "contexttestcmdpathscope",
|
||||
"context": {
|
||||
"cluster": "clustertestcmdpath",
|
||||
"user": "usertestcmdpathscope"
|
||||
}
|
||||
}
|
||||
],
|
||||
"clusters": [
|
||||
{
|
||||
@ -588,6 +612,10 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
"insecure-skip-tls-verify": True,
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clustertestcmdpath",
|
||||
"cluster": {}
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
@ -661,7 +689,8 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
"auth-provider": {
|
||||
"config": {
|
||||
"access-token": TEST_AZURE_TOKEN,
|
||||
"apiserver-id": "00000002-0000-0000-c000-000000000000",
|
||||
"apiserver-id": "00000002-0000-0000-c000-"
|
||||
"000000000000",
|
||||
"environment": "AzurePublicCloud",
|
||||
"refresh-token": "refreshToken",
|
||||
"tenant-id": "9d2ac018-e843-4e14-9e2b-4e0ddac75433"
|
||||
@ -676,7 +705,8 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
"auth-provider": {
|
||||
"config": {
|
||||
"access-token": TEST_AZURE_TOKEN,
|
||||
"apiserver-id": "00000002-0000-0000-c000-000000000000",
|
||||
"apiserver-id": "00000002-0000-0000-c000-"
|
||||
"000000000000",
|
||||
"environment": "AzurePublicCloud",
|
||||
"expires-in": "0",
|
||||
"expires-on": "156207275",
|
||||
@ -693,7 +723,8 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
"auth-provider": {
|
||||
"config": {
|
||||
"access-token": TEST_AZURE_TOKEN,
|
||||
"apiserver-id": "00000002-0000-0000-c000-000000000000",
|
||||
"apiserver-id": "00000002-0000-0000-c000-"
|
||||
"000000000000",
|
||||
"environment": "AzurePublicCloud",
|
||||
"expires-in": "0",
|
||||
"expires-on": "2018-10-18 00:52:29.044727",
|
||||
@ -710,7 +741,8 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
"auth-provider": {
|
||||
"config": {
|
||||
"access-token": TEST_AZURE_TOKEN,
|
||||
"apiserver-id": "00000002-0000-0000-c000-000000000000",
|
||||
"apiserver-id": "00000002-0000-0000-c000-"
|
||||
"000000000000",
|
||||
"environment": "AzurePublicCloud",
|
||||
"expires-in": "0",
|
||||
"expires-on": "2018-10-18 00:52",
|
||||
@ -727,7 +759,8 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
"auth-provider": {
|
||||
"config": {
|
||||
"access-token": TEST_AZURE_TOKEN,
|
||||
"apiserver-id": "00000002-0000-0000-c000-000000000000",
|
||||
"apiserver-id": "00000002-0000-0000-c000-"
|
||||
"000000000000",
|
||||
"environment": "AzurePublicCloud",
|
||||
"expires-in": "0",
|
||||
"expires-on": "-1",
|
||||
@ -877,6 +910,40 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "usertestcmdpath",
|
||||
"user": {
|
||||
"auth-provider": {
|
||||
"name": "gcp",
|
||||
"config": {
|
||||
"cmd-path": "cmdtorun"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "usertestcmdpathempty",
|
||||
"user": {
|
||||
"auth-provider": {
|
||||
"name": "gcp",
|
||||
"config": {
|
||||
"cmd-path": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "usertestcmdpathscope",
|
||||
"user": {
|
||||
"auth-provider": {
|
||||
"name": "gcp",
|
||||
"config": {
|
||||
"cmd-path": "cmd",
|
||||
"scopes": "scope"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1279,6 +1346,48 @@ class TestKubeConfigLoader(BaseTestCase):
|
||||
active_context="exec_cred_user").load_and_set(actual)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_user_cmd_path(self):
|
||||
A = namedtuple('A', ['token', 'expiry'])
|
||||
token = "dummy"
|
||||
return_value = A(token, parse_rfc3339(datetime.datetime.now()))
|
||||
CommandTokenSource.token = mock.Mock(return_value=return_value)
|
||||
expected = FakeConfig(api_key={
|
||||
"authorization": BEARER_TOKEN_FORMAT % token})
|
||||
actual = FakeConfig()
|
||||
KubeConfigLoader(
|
||||
config_dict=self.TEST_KUBE_CONFIG,
|
||||
active_context="contexttestcmdpath").load_and_set(actual)
|
||||
del actual.get_api_key_with_prefix
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_user_cmd_path_empty(self):
|
||||
A = namedtuple('A', ['token', 'expiry'])
|
||||
token = "dummy"
|
||||
return_value = A(token, parse_rfc3339(datetime.datetime.now()))
|
||||
CommandTokenSource.token = mock.Mock(return_value=return_value)
|
||||
expected = FakeConfig(api_key={
|
||||
"authorization": BEARER_TOKEN_FORMAT % token})
|
||||
actual = FakeConfig()
|
||||
self.expect_exception(lambda: KubeConfigLoader(
|
||||
config_dict=self.TEST_KUBE_CONFIG,
|
||||
active_context="contexttestcmdpathempty").load_and_set(actual),
|
||||
"missing access token cmd "
|
||||
"(cmd-path is an empty string in your kubeconfig file)")
|
||||
|
||||
def test_user_cmd_path_with_scope(self):
|
||||
A = namedtuple('A', ['token', 'expiry'])
|
||||
token = "dummy"
|
||||
return_value = A(token, parse_rfc3339(datetime.datetime.now()))
|
||||
CommandTokenSource.token = mock.Mock(return_value=return_value)
|
||||
expected = FakeConfig(api_key={
|
||||
"authorization": BEARER_TOKEN_FORMAT % token})
|
||||
actual = FakeConfig()
|
||||
self.expect_exception(lambda: KubeConfigLoader(
|
||||
config_dict=self.TEST_KUBE_CONFIG,
|
||||
active_context="contexttestcmdpathscope").load_and_set(actual),
|
||||
"scopes can only be used when kubectl is using "
|
||||
"a gcp service account key")
|
||||
|
||||
|
||||
class TestKubernetesClientConfiguration(BaseTestCase):
|
||||
# Verifies properties of kubernetes.client.Configuration.
|
||||
@ -1421,6 +1530,37 @@ class TestKubeConfigMerger(BaseTestCase):
|
||||
TEST_KUBE_CONFIG_PART4 = {
|
||||
"current-context": "no_user",
|
||||
}
|
||||
# Config with user having cmd-path
|
||||
TEST_KUBE_CONFIG_PART5 = {
|
||||
"contexts": [
|
||||
{
|
||||
"name": "contexttestcmdpath",
|
||||
"context": {
|
||||
"cluster": "clustertestcmdpath",
|
||||
"user": "usertestcmdpath"
|
||||
}
|
||||
}
|
||||
],
|
||||
"clusters": [
|
||||
{
|
||||
"name": "clustertestcmdpath",
|
||||
"cluster": {}
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"name": "usertestcmdpath",
|
||||
"user": {
|
||||
"auth-provider": {
|
||||
"name": "gcp",
|
||||
"config": {
|
||||
"cmd-path": "cmdtorun"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def _create_multi_config(self):
|
||||
files = []
|
||||
@ -1428,7 +1568,8 @@ class TestKubeConfigMerger(BaseTestCase):
|
||||
self.TEST_KUBE_CONFIG_PART1,
|
||||
self.TEST_KUBE_CONFIG_PART2,
|
||||
self.TEST_KUBE_CONFIG_PART3,
|
||||
self.TEST_KUBE_CONFIG_PART4):
|
||||
self.TEST_KUBE_CONFIG_PART4,
|
||||
self.TEST_KUBE_CONFIG_PART5):
|
||||
files.append(self._create_temp_file(yaml.safe_dump(part)))
|
||||
return ENV_KUBECONFIG_PATH_SEPARATOR.join(files)
|
||||
|
||||
@ -1439,7 +1580,11 @@ class TestKubeConfigMerger(BaseTestCase):
|
||||
{'context': {'cluster': 'ssl', 'user': 'ssl'}, 'name': 'ssl'},
|
||||
{'context': {'cluster': 'default', 'user': 'simple_token'},
|
||||
'name': 'simple_token'},
|
||||
{'context': {'cluster': 'default', 'user': 'expired_oidc'}, 'name': 'expired_oidc'}]
|
||||
{'context': {'cluster': 'default', 'user': 'expired_oidc'},
|
||||
'name': 'expired_oidc'},
|
||||
{'context': {'cluster': 'clustertestcmdpath',
|
||||
'user': 'usertestcmdpath'},
|
||||
'name': 'contexttestcmdpath'}]
|
||||
|
||||
contexts, active_context = list_kube_config_contexts(
|
||||
config_file=kubeconfigs)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user