Skip to content
This repository has been archived by the owner on Mar 13, 2022. It is now read-only.

Commit

Permalink
Refresh GCP tokens on retrieval by overriding client config method.
Browse files Browse the repository at this point in the history
[Fix #59]
  • Loading branch information
TrevorEdwards committed Oct 20, 2018
1 parent 339fffc commit c89ff54
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 15 deletions.
16 changes: 16 additions & 0 deletions config/kube_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,24 @@ def _load_cluster_info(self):
if 'insecure-skip-tls-verify' in self._cluster:
self.verify_ssl = not self._cluster['insecure-skip-tls-verify']

def _using_gcp_auth_provider(self):
return self._user and \
'auth-provider' in self._user and \
'name' in self._user['auth-provider'] and \
self._user['auth-provider']['name'] == 'gcp'

def _set_config(self, client_configuration):
if self._using_gcp_auth_provider():
# GCP auth tokens must be refreshed regularly, but swagger expects
# a constant token. Replace the swagger-generated client config's
# get_api_key_with_prefix method with our own to allow automatic
# token refresh.
def _gcp_get_api_key(*args):
return self._load_gcp_token(self._user['auth-provider'])
client_configuration.get_api_key_with_prefix = _gcp_get_api_key
if 'token' in self.__dict__:
# Note: this line runs for GCP auth tokens as well, but this entry
# will not be updated upon GCP token refresh.
client_configuration.api_key['authorization'] = self.token
# copy these keys directly from self to configuration object
keys = ['host', 'ssl_ca_cert', 'cert_file', 'key_file', 'verify_ssl']
Expand Down
91 changes: 76 additions & 15 deletions config/kube_config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@

EXPIRY_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
# should be less than kube_config.EXPIRY_SKEW_PREVENTION_DELAY
EXPIRY_TIMEDELTA = 2
PAST_EXPIRY_TIMEDELTA = 2
# should be more than kube_config.EXPIRY_SKEW_PREVENTION_DELAY
FUTURE_EXPIRY_TIMEDELTA = 60

NON_EXISTING_FILE = "zz_non_existing_file_472398324"

Expand All @@ -47,9 +49,9 @@ def _format_expiry_datetime(dt):
return dt.strftime(EXPIRY_DATETIME_FORMAT)


def _get_expiry(loader):
def _get_expiry(loader, active_context):
expired_gcp_conf = (item for item in loader._config.value.get("users")
if item.get("name") == "expired_gcp")
if item.get("name") == active_context)
return next(expired_gcp_conf).get("user").get("auth-provider") \
.get("config").get("expiry")

Expand All @@ -73,8 +75,11 @@ def _raise_exception(st):
TEST_PASSWORD = "pass"
# token for me:pass
TEST_BASIC_TOKEN = "Basic bWU6cGFzcw=="
TEST_TOKEN_EXPIRY = _format_expiry_datetime(
datetime.datetime.utcnow() - datetime.timedelta(minutes=EXPIRY_TIMEDELTA))
DATETIME_EXPIRY_PAST = datetime.datetime.utcnow(
) - datetime.timedelta(minutes=PAST_EXPIRY_TIMEDELTA)
DATETIME_EXPIRY_FUTURE = datetime.datetime.utcnow(
) + datetime.timedelta(minutes=FUTURE_EXPIRY_TIMEDELTA)
TEST_TOKEN_EXPIRY_PAST = _format_expiry_datetime(DATETIME_EXPIRY_PAST)

TEST_SSL_HOST = "https://test-host"
TEST_CERTIFICATE_AUTH = "cert-auth"
Expand Down Expand Up @@ -371,6 +376,13 @@ class TestKubeConfigLoader(BaseTestCase):
"user": "expired_gcp"
}
},
{
"name": "expired_gcp_refresh",
"context": {
"cluster": "default",
"user": "expired_gcp_refresh"
}
},
{
"name": "oidc",
"context": {
Expand Down Expand Up @@ -509,7 +521,24 @@ class TestKubeConfigLoader(BaseTestCase):
"name": "gcp",
"config": {
"access-token": TEST_DATA_BASE64,
"expiry": TEST_TOKEN_EXPIRY, # always in past
"expiry": TEST_TOKEN_EXPIRY_PAST, # always in past
}
},
"token": TEST_DATA_BASE64, # should be ignored
"username": TEST_USERNAME, # should be ignored
"password": TEST_PASSWORD, # should be ignored
}
},
# Duplicated from "expired_gcp" so test_load_gcp_token_with_refresh
# is isolated from test_gcp_get_api_key_with_prefix.
{
"name": "expired_gcp_refresh",
"user": {
"auth-provider": {
"name": "gcp",
"config": {
"access-token": TEST_DATA_BASE64,
"expiry": TEST_TOKEN_EXPIRY_PAST, # always in past
}
},
"token": TEST_DATA_BASE64, # should be ignored
Expand Down Expand Up @@ -630,16 +659,20 @@ def test_load_user_token(self):
self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, loader.token)

def test_gcp_no_refresh(self):
expected = FakeConfig(
host=TEST_HOST,
token=BEARER_TOKEN_FORMAT % TEST_DATA_BASE64)
actual = FakeConfig()
fake_config = FakeConfig()
# swagger-generated config has this, but FakeConfig does not.
self.assertFalse(hasattr(fake_config, 'get_api_key_with_prefix'))
KubeConfigLoader(
config_dict=self.TEST_KUBE_CONFIG,
active_context="gcp",
get_google_credentials=lambda: _raise_exception(
"SHOULD NOT BE CALLED")).load_and_set(actual)
self.assertEqual(expected, actual)
"SHOULD NOT BE CALLED")).load_and_set(fake_config)
# Should now be populated with a gcp token fetcher.
self.assertIsNotNone(fake_config.get_api_key_with_prefix)
self.assertEqual(TEST_HOST, fake_config.host)
# For backwards compatibility, authorization field should still be set.
self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64,
fake_config.api_key['authorization'])

def test_load_gcp_token_no_refresh(self):
loader = KubeConfigLoader(
Expand All @@ -654,20 +687,48 @@ def test_load_gcp_token_no_refresh(self):
def test_load_gcp_token_with_refresh(self):
def cred(): return None
cred.token = TEST_ANOTHER_DATA_BASE64
cred.expiry = datetime.datetime.now()
cred.expiry = datetime.datetime.utcnow()

loader = KubeConfigLoader(
config_dict=self.TEST_KUBE_CONFIG,
active_context="expired_gcp",
get_google_credentials=lambda: cred)
original_expiry = _get_expiry(loader)
original_expiry = _get_expiry(loader, "expired_gcp")
self.assertTrue(loader._load_auth_provider_token())
new_expiry = _get_expiry(loader)
new_expiry = _get_expiry(loader, "expired_gcp")
# assert that the configs expiry actually updates
self.assertTrue(new_expiry > original_expiry)
self.assertEqual(BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64,
loader.token)

def test_gcp_get_api_key_with_prefix(self):
class cred_old:
token = TEST_DATA_BASE64
expiry = DATETIME_EXPIRY_PAST

class cred_new:
token = TEST_ANOTHER_DATA_BASE64
expiry = DATETIME_EXPIRY_FUTURE
fake_config = FakeConfig()
_get_google_credentials = mock.Mock()
_get_google_credentials.side_effect = [cred_old, cred_new]

loader = KubeConfigLoader(
config_dict=self.TEST_KUBE_CONFIG,
active_context="expired_gcp_refresh",
get_google_credentials=_get_google_credentials)
loader.load_and_set(fake_config)
original_expiry = _get_expiry(loader, "expired_gcp_refresh")
# Call GCP token fetcher.
token = fake_config.get_api_key_with_prefix()
new_expiry = _get_expiry(loader, "expired_gcp_refresh")

self.assertTrue(new_expiry > original_expiry)
self.assertEqual(BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64,
loader.token)
self.assertEqual(BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64,
token)

def test_oidc_no_refresh(self):
loader = KubeConfigLoader(
config_dict=self.TEST_KUBE_CONFIG,
Expand Down

0 comments on commit c89ff54

Please sign in to comment.